All Visual Basic Tips



A caveat for the FileSystemObject Delete methods in VB

As you know, the Microsoft Scripting Runtime object library makes it easy to manipulate files and folders. With the DeleteFolder and DeleteFile methods, you can eliminate unwanted items. However, these methods will abort if they encounter an error--and they don't rollback the changes they made up to that point. For instance, if a method deletes two folders out of ten and an error occurs, VB aborts the operation and only the first two items will have been deleted. Several FileSystemObject methods operate this way.

ZD Tips



A function for numbers beginning with 0

Here is a function that is very helpful when you need some number that begins with 0's and has to be a certain number of digits:

Function PadToString(intValue, intDigits)
    PadToString = String(intDigits - Len(intValue), "0") & intValue
End Function

Usage:

myNewStr = PadToString(702, 6)

myNewStr would be "000702"

Christopher Dickens



A quick VB technique to check for existing items in a list

If you wish to determine if a list contains a particular item, you might think to build an array, then traverse the array, searching for each item that you want to find. As a faster alternative for shorter lists, use a delimited string instead of an array. For instance, suppose you have a listbox and want to test to see if it already contains an user-defined entry. First, loop through the list and build a string,

For X = 0 To List1.ListCount - 1
    strItemList = strItemList & "[" & List1.List(X) & "]"
Next X

Then, use the VB Instr() function to determine if the new string contains a particular item, as in

If InStr(strItemList, "[" & strTestItem & "]") Then
    MsgBox "Already there."
Else
    MsgBox "Added..."
End If

Indar Bhola



A tip for filling a ComboBox during an application startup

Visual Basic is designed to fire the click event of a combobox when setting the current item. If you are filling a combobox during an application startup, place the following code in the Form_Load event:

' Make Combobox invisible
Combo1.Visible = False

' Your routine to fill the combo box
FillComboBox

'-Make combobox visible
Combo1.Visible = True

In the Combo1_Click event only execute the code if the combobox is visible:

If Combo1.Visible = True Then
' Your code
End If

Bernard Ho-jim



Add a new item to a form's System menu

Often, you may want to add a single item to a form's control box. To do so, you'll need to use three API functions, GetSystemMenu, AppendItemMenu, and DrawMenuBar, as follows:

Option Explicit
Private Declare Function DrawMenuBar Lib "user32" _
(ByVal hWnd As Long) As Long

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

Private Declare Function AppendMenu Lib "user32" Alias _
    "AppendMenuA" (ByVal hMenu As Long, ByVal wFlags _
    As Long, ByVal wIDNewItem As Long, ByVal lpNewItem _
    As Any) As Long

Const MF_STRING = &H0&

Private Sub Form_Load()
Dim SysMenu As Long
  SysMenu = GetSystemMenu(Me.hWnd, False)

  If SysMenu Then
    AppendMenu SysMenu, MF_STRING, 0, "MyItem"
    DrawMenuBar Me.hWnd
  End If
End Sub

As you can see, the procedure retrieves a handle to a copy of the current form's System menu. Next, it adds the string, "MyItem", to the list. Finally, it redraws the menu to include the new item. You must issue the DrawMenuBar command whenever you make changes to a menu bar, even if the menu bar isn't currently visible. Of course, just because you've added text to the system menu, doesn't mean it does anything. The AppendMenu function offers several different parameters that define the item you'd like to add. See the Help file for a complete list. Also keep in mind that the AppendMenu function only adds an item to the bottom of a menu, and has been superceded by the InsertMenuItem function. However, while offering much more control, this new API function is also much more complicated. If you simply need to add an additional item to the bottom of a menu bar, then AppendMenu does just fine.

ZD Tips



Add a new line to existing textbox text

Often, you may want to append additional information to existing text within a multiline textbox. For instance, suppose you want to add the string "Updated: " followed by the current date. To do so, you can take advantage of the SelStart and SelText properties. As you probably know, the SelStart property returns or sets the beginning of a selection. The SelText returns or sets the actual selected text. If there isn't a selection, then both properties return the insertion point. So, to insert a new line of text in a multiline textbox, use code similar to:

Dim strNewText As String
With Text1
strNewText = "Updated: " & Date
    .SelStart = Len(.Text)
    .SelText = vbNewLine & strNewText
End With

This code snippet moves the insertion point to the end of any existing text in Text1, then inserts a new line followed by the additional information.

ZD Tips



Add and remove images in an imagelist assigned to a toolbar

Keep the flexibility to add and remove images in a imagelist that is assigned to a toolbar while a project is still in design mode. Because you can't alter images in a imagelist control while it's assigned to a toolbar, you can use this alternative way to bypass this limitation.

1. Add bitmaps to the imagelist control and assign a unique key to every added bitmap.

2. Add the buttons to the toolbar and assign the key from the bitmap image to the button key. Each button with a bitmap should have the same key as the image key in the imagelist. Each button without a bitmap, i.e. tbrSeparator or tbrPlaceholder, should have NO key.

3. In the form load event assign the imagelist to the toolbar:

Set ToolBar1.ImageList = ImageList1

4. Assign the bitmap images to the buttons on the toolbar:

Dim myButton as Variant

For Each myButton in ToolBar1.Buttons
   If myButton.Key <> Empty Then
      myButton.Image = myButton.Key
      'if the key name is human readable
      'use it for description and tooltiptext
      myButton.Description = myButton.Key
      myButton.ToolTipText = myButton.Key
   Endif
Next

Robert Stamm



Add controls to a VB control array at run-time

As you probably know, a control array lets you create controls that share the same name and events. They also use fewer resources than do the same number of controls not part of a control array. Often, you may want to add a control, such as a button, to a control array at runtime. To do so, you use the Load statement, which takes the following syntax

Load object(index)

where object is the name of the control array, and index is the index number of the new control you want to add. In order to add controls to a control array at runtime, however, you must have at least one control already in the array, (with it's index property set-most likely to 0). VB only allows 32,767 controls in an array.

For example, suppose you have a form with a button control array named cmdBtn. On the button's Click event, you want to add another button to the form. To illustrate, open a new project and add a command button to the default form. In the Properties Window, enter 0 for the control's Index. When you do, VB transforms the button into a control array. Now, add the following code to the form:

Private Sub cmdBtn_Click(Index As Integer)
Dim btn As CommandButton
Dim iIndex As Integer
iIndex = cmdBtn.Count
If iIndex <= 32767 Then
    Load cmdBtn(iIndex)
    Set btn = cmdBtn(iIndex)
    With btn
        .Top = cmdBtn(iIndex - 1).Top + 620
        .Caption = "Command" & iIndex + 1
        .Visible = True
    End With
    Set btn = Nothing
End If
End Sub

When you run the form, and click the button, the procedure adds a new button to the form.

ZD Tips



Add Dithered Backgrounds to your VB Forms

Ever wonder how the SETUP.EXE screen gets its cool shaded background coloring? This color shading is called dithering, and you can easily incorporate it into your forms. Add the following routine to a form:

     Sub Dither(vForm As Form)
     Dim intLoop As Integer
         vForm.DrawStyle = vbInsideSolid
         vForm.DrawMode = vbCopyPen
         vForm.ScaleMode = vbPixels
         vForm.DrawWidth = 2
         vForm.ScaleHeight = 256
         For intLoop = 0 To 255
           vForm.Line (0, intLoop)-(Screen.Width, intLoop _
                       - 1), RGB(0, 0, 255 -intLoop), B
         Next intLoop
     End Sub

Now, add to the Form_Activate event the line

     Dither ME

This version creates a fading blue background by adjusting the blue value in the RGB function. (RGB stands for Red-Green-Blue.) You can create a fading red background by changing the RGB call to

     RGB(255 - intLoop, 0, 0).

Barron Anderson



An ActiveX control that restricts a VB form's dimensions

When you create a VB form, you'll often need to prevent a user from resizing it past a certain dimension. To this end, you can easily use a form's Resize event and test to see if the form's current dimensions exceed a predetermined twip value. If they do, then VB can set the form's dimension back to the maximum value, something like:

Private Sub Form_Resize()
  If Me.Width > 2800 Then Me.Width = 2800
End Sub

However, this method still lets a user resize the form beyond the specified dimensions--it simply changes the form back after they release the mouse button. Also, click the form's maximize button, and the form extends to match the screen's entire area. Of course, VB provides several ways to handle this behavior programmatically, but they don't flow very well. Often a form flickers all over the screen as the code moves and changes its dimensions. GUI guidelines suggest that a user is never wrong; therefore, it would be better to prevent a user from resizing a form past a maximum value to begin with.

Fortunately, Alvaro Redondo has created a free ActiveX control, called th ARFormExtender to take care of this for you. Drop this invisible control on a form, set the MaxHeight, MinHeight, MaxWidth, and MinWidth properties, and voila...when you run the form, you won't even be able to drag the form beyond the specified dimensions. It will conform to them even if you maximize the form.

In addition, the ARFormExtender exposes a ResizeContents property. If you set this property to True, ARFormExtender resizes and repositions all the controls on the form when you resize it. Also, any change to the form's dimensions triggers ARFormExtender's own Resize event, which passes the WidthChange and HeightChange parameters. These two values indicate the vertical and horizontal change (in twips) since the last time the event fired. To download this free ActiveX, or to see a list of the other form enhancements this control offers, visit

http://www.sevillaonline.com/ActiveX/

ZD Tips



An easy way to remember VB form start-up events

If you have a hard time remembering a VB form's sequence of events on start-up, just keep in mind this little phrase:

I Like RAP

or

I Initialize Like Load R Resize A Activate P Paint

It's somewhat better than All Cows Eat Grass, right? Of course, if you're not too thrilled about admitting you like rap, you can always change it to 'I loathe rap', instead.

Lorenzo Benaglia



Be sure to Close all Data Objects upon Exit

If you use any data objects in your code (DAO, RDO, or ADO), you should be sure to explicitly close all open recordsets, databases, and workspaces before you exit. Even though the pointers to these objects are automatically destroyed when you exit the program, if you fail to explicitly close all open items, your database connections may not be immediately released and the memory used by these objects may never be re-allocated by the operating system. Here's a short routine you can add to your Form_Unload event (or some other terminating code module) that will close all open DAO workspaces, databases, and recordsets and release the memory reserved by these objects. This code will work whether you have 1, 100, or even no connections open when you attempt to exit the form.

Private Sub Form_Unload(Cancel As Integer)
    '
    ' *** close out db objects
    ' *** and release all memory
    '
    On Error Resume Next
    '
    Dim ws As Workspace
    Dim db As Database
    Dim rs As Recordset
    '
    For Each ws In Workspaces
        For Each db In ws.Databases
            For Each rs In db.Recordsets
                rs.Close
                Set rs = Nothing
            Next
            db.Close
            Set db = Nothing
        Next
        ws.Close
        Set ws = Nothing
    Next
    '
End Sub

Inside Visual Basic



Case sensitivity in DLL calls

Use the Alias keyword to help convert non-case-sensitive VB 3.0 function calls to their case-sensitive 32-bit counterparts.

When you declare or call a DLL in 32-bit Visual Basic, the name of the function is case sensitive. To convert non-case-sensitive VB 3.0 calls to case-sensitive calls, use the Alias keyword to hold the case-sensitive function name. Place the name you want to call the function after the Declare Sub/Function statement. (The Win32API.TXT file Aliases all function calls, eliminating the case-sensitivity problem.)

Stefanie R. Kushner



Case-conversion on the fly

If you want to convert text to uppercase as it's entered in a text box, just create an Upper function and call it from the text box's keypress event, as shown here:

  Private Sub Text1_KeyPress(KeyAscii As Integer)
     KeyAscii = Upper(KeyAscii)
  End Sub

  Function Upper(KeyAscii As Integer)
     If KeyAscii > 96 And KeyAscii < 123 Then
          KeyAscii = KeyAscii - 32
     End If
     Upper = KeyAscii
  End Function

This technique eliminates the need to "UCase" entered data. It also makes "hotseek" data searches much easier.

Inside Visual Basic



Change VB's default OLE request pending message

When Visual Basic receives mouse or keyboard input while an automation request is pending, it displays a default Component Request Pending dialog box. This dialog box includes text and a Switch To button which are intended for use with visible ActiveX components such as Microsoft Excel. However, this message box isn't always appropriate. For example, a program may call the method of an object that doesn't have a user interface. Or perhaps, the application has called an ActiveX component created with Remote Automation features, in which case it's probably running on another computer.

As an alternative, you can set an application's OLERequestPendingMsgText property. This property lets you replace the default Component Request Pending dialog box with an alternate message, and message box. For instance, using

App.OLERequestPendingMsgText = "Hold your horses, I'm workin' on it...."

generates a simple message box with the above text and an OK button.

Momar Fall



Clearing all fields and combo boxes on a form

Sometimes you want to clear all the fields and combo boxes on a data-entry form. If your form contains many controls, this could become tedious and error prone. The following subroutine clears the contents of such fields on your form automatically:

Public Sub ClearAllControls(frmForm As Form)
Dim ctlControl As Object

    ' Initialize all controls that can be initialized
    ' Any control with a text property or a list-index property
    On Error Resume Next
    For Each ctlControl In frmForm.Controls
        ctlControl.Text = ""
        ctlControl.ListIndex = -1
        DoEvents
    Next ctlControl

End Sub

Just call this procedure from your code like this:

Call ClearAllControls(Me)

John Baumbach



Clearing all text boxes on your form

By using following code you can clear all the text boxes on your form

Dim vControlFor Each vControl In Me.Controls    'where Me is the form
  If TypeOf vControl Is TextBox Then
    vControl.Text = ""
  End If
Next

Gurpreet Singh



Code After a Call to Form Unload will Prevent the Form from Closing

If you use control arrays to handle common button or menu activities, you might be tempted to make one of the buttons call the "Unload Me" event to end the form execution, as follows:

Private Sub cmdAction_Click(Index As Integer)
    '
    ' *** this doesn't work!
    '
    Select Case Index
        Case 0: Me.WindowState = vbMaximized
        Case 1: Me.WindowState = vbNormal
        Case 2: Unload Me
    End Select
    '
    MsgBox "Current state: " & CStr(Me.WindowState)
    '
End Sub

However, this code won't work. Since there's a line of executing code after the "Unload Me" statement, the form will hide, but never unload!

To fix this, you must replace the call to "Unload Me" with a flag variable and execute the "Unload Me" as the very last action in the method:

Private Sub cmdAction_Click(Index As Integer)
    '
    ' *** this works!
    '
    Dim blnUnload As Boolean
    '
    Select Case Index
        Case 0: Me.WindowState = vbMaximized
        Case 1: Me.WindowState = vbNormal
        Case 2: blnUnload = True
    End Select
    '
    MsgBox "Current state: " & CStr(Me.WindowState)
    '
    If blnUnload = True Then
        Unload Me
    End If
    '
End Sub

Mike Amundsen



Compact long VB filepaths with SHLWAPI library

The PathCompactPath function in SHLWAPI provides one way to compact long filenames. It does so by replacing a portion of the pathname with an ellipsis (...). This function uses the following API declaration statement:

 
Private Declare Function _ 
PathCompactPath Lib "shlwapi"_ 
Alias "PathCompactPathA" _ 
(ByVal hDC As Long, ByVal _ 
lpszPath As String, _ 
ByVal dx As Long) As Long 

As you can see, the PathCompactPath function requires three arguments. The first argument contains a device context handle. The second argument holds the address of the pathname you want to use. The third argument contains the width in pixels of the spot in which you want the pathname to fit. So, to place a compacted filename in a label named lblEllipsis, place the following in a command button's Click() event:

 
Private Sub Command1_Click() 
Dim lhDC As Long, lCtlWidth As Long 
Dim FileSpec As String 

FileSpec =  "C:\MyFolder\VisualBasic\MyReallyWayTooLongFolderName\" _ 
& "ButWhoCares\IhaveTheAPI.doc" 
Me.ScaleMode = vbPixels 
lCtlWidth = lblEllipsis.Width - Me.DrawWidth 
lhDC = Me.hDC 
PathCompactPath lhDC, FileSpec, lCtlWidth 
lblEllipsis.Caption = FileSpec 
End Sub 

ZD Tips



Confirm Screen Resolution

Here's a great way to stop the user from running your application in the wrong screen resolution. First, create a function called CheckRez:

Public Function CheckRez(pixelWidth As Long, pixelHeight As Long) As Boolean
    '
    Dim lngTwipsX As Long
    Dim lngTwipsY As Long
    '
    ' convert pixels to twips
    lngTwipsX = pixelWidth * 15
    lngTwipsY = pixelHeight * 15
    '
    ' check against current settings
    If lngTwipsX <> Screen.Width Then
        CheckRez = False
    Else
        If lngTwipsY <> Screen.Height Then
            CheckRez = False
        Else
            CheckRez = True
        End If
    End If
    '
End Function

Next, run the following code at the start of the program:

    If CheckRez(640, 480) = False Then
        MsgBox "Incorrect screen size!"
    Else
        MsgBox "Screen Resolution Matches!"
    End If

Nicholas L. Otley



Create templates from existing forms

Here's a way for you to create a template from an existing form so you can use it to design future forms. Once you've created the 'perfect' form, place it in the ..\Template\Forms\ subdirectory. Now, the next time you want to add a new form to your project, you'll see that the newly created form is in the list of available form templates! Just select it and you're on your way. This tip also applies to modules, classes, etc.

Ken Halter



Creating a incrementing number box

You can't increment a vertical scroll bar's value--a fact that can become annoying. For example, start a new project and place a text box and a vertical scroll bar on the form. Place the vertical scroll bar to the right of the text box and assign their Height and Top properties the same values. Assign the vertical scroll bar a Min property value of 1 and a Max value of 10. Place the following code in the vertical scroll bar's Change event:

Text1.Text = VScroll1.Value

Now press [F5] to run the project. Notice that if you click on the bottom arrow of the vertical scroll bar, the value increases; if you click on the top arrow, the value decreases. >From my perspective, it should be the other way around.

To correct this, change the values of the Max and Min properties to negative values. For example, end the program and return to the design environment. Change the vertical scroll bar’s Max value to -1 and its Min value to -10. In its Change event, replace the line you entered earlier with the following:

Text1.Text = Abs(Vscroll1.Value)

Now press [F5] to run the project. When you click on the top arrow of the vertical scroll bar, the value now increases. Adjust the Height properties of the text box and the scroll bar so you can't see the position indicator, and your number box is ready to go.

Bryan Shoemaker



Creating a new context menu in editable controls

This routine will permit you to replace the original context menu with your private context menu in an editable control.

Add the following code to your form or to a BAS module:

Private Const WM_RBUTTONDOWN = &H204
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA"
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As
Any) As Long

Public Sub OpenContextMenu(FormName As Form, MenuName As Menu)

    'Tell system we did a right-click on the mdi
    Call SendMessage(FormName.hwnd, WM_RBUTTONDOWN, 0, 0&)
    'Show my context menu
    FormName.PopupMenu MenuName
    '
End Sub

Next, use the Visual Basic Menu Editor and the table below to create a simple menu.

Caption         Name            Visible
Context Menu    mnuContext      NO
...First Item   mnuContext1
...Second Item  mnuContext2

Note that the last two items in the menu are indented (...) one level and that only the first item in the list ("Context Menu") has the Visible property set to NO.

Now add a text box to your form and enter the code below in the MouseDown event of the text box.

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

    If Button = vbRightButton Then
        Call OpenContextMenu(Me, Me.mnuContext)
    End If

End Sub

Note: If you just want to kill the system context menu, just comment out the line:

    FormName.PopupMenu MenuName

in the OpenContextMenu routine.

Antonio Almeida



Creating a ScreenSaver

If you've ever wanted to create your own ScreenSaver in Visual Basic, you're in luck! Start off by creating a new standard EXE project. Add a label to your form with text. In addition, add a Timer control with the interval property set to 1000. Then add the following code to your form:

Private Sub Form_Click()
  '-- End Screen Saver when form is clicked
  Unload Me
End Sub

Private Sub Form_Load()
  '-- Do not allow more than 1 instance of the Screen Saver
  If App.PrevInstance Then Unload Me
End Sub

Private Sub Timer1_Timer()
  '-- Make label blink once a second
  Label1.Visible = Not (Label1.Visible)
End Sub

Make sure your Form's Maximized property is set, and that it's Border Style is set to None. Most screen savers are full screen, without a title bar.

In the File/Make EXE File dialog, under the Options button, type the string SCRNSAVE: (all in upper case) at the beginning of the Application Title textbox. (For example, we could call our application SCRNSAVE:TestApp1.) For the application executable filename, specify that the extension of the application be .SCR instead of .EXE. (For example, we could call our executable TestApp1.scr.) Make sure you put the .SCR file in your Windows Directory.

Inside Visual Basic



Creating professional documents with code

Have you ever wanted to create polished, professional documents, like those created in Microsoft Word, through the use of code? Follow these easy steps to make it happen:

1.) Add a reference to your project for "Microsoft Word 8.0 Object Library" (MSWORD8.OLB).

2.) Add the following code to create an instance of Word and add text to a new document:

Dim objWord As New Word.Application

'-- Show Microsoft Word
objWord.Visible = True

'-- Add new Document
objWord.Documents.Add

'-- Add text to Document
objWord.Selection.TypeText "Visual Basic!"

'-- Select all Text
objWord.Selection.WholeStory

'-- Change Font Size
objWord.Selection.Font.Size = 50

Set objWord = Nothing

3.) Be sure to check out the Object Browser for more properties and methods exposed by the Word Object.

Carol Lewis



Creating Short Arrays Using the Variant Data Type

If you need to create a short list of items in an array, you can save a lot of coding by using the Variant data type instead of a dimensioned standard data type. This is especially handy when you need to create a list of short phrases to support numeric output.

For example, add a button to a standard VB form and paste the following code into the Click event of the button:

Private Sub Command1_Click()
    '
    ' create a quick array using variants
    '
    Dim aryList As Variant
    '
    aryList = Array("No Access", "Read-Only", "Update", "Delete")
    '
    MsgBox aryList(2)
    '
End Sub

Michael C. Amundsen



Creating Win32 region windows

The Win32 API includes a really amazing feature called region windows. A window under Win32 no longer has to be rectangular! In fact, it can be any shape that may be constructed using Win32 region functions. Using the SetWindowRgn Win32 function from within VB is so simple, but the results are unbelievable! The following example shows a VB form that is NOT rectangular!!

Here is the code. Enjoy!

' This goes into the General Declarations section:

Private Declare Function CreateEllipticRgn Lib "gdi32" _
 (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, _
 ByVal Y2 As Long) As Long

Private Declare Function SetWindowRgn Lib "user32" _
 (ByVal hWnd As Long, ByVal hRgn As Long, _
 ByVal bRedraw As Boolean) As Long

Private Sub Form_Load()

    Show 'The form!
    SetWindowRgn hWnd, CreateEllipticRgn(0, 0, 300, 200), True

End Sub

AlMoataz B. Ahmed



Customizing a text box's pop-up menu

In Windows 95, right-clicking any text box brings up a context menu with basic edit commands on it. If you want to change this menu, put the following code in the MouseDown event of the text box.

If Button = vbRightButton Then
   Text1.Enabled = False
   Text1.Enabled = True
   Text1.SetFocus
   PopUpMenu Menu1
End If

where Text1 is the text box and Menu1 is the pop-up menu.

Disabling and re-enabling the control causes Windows to lose the MouseDown message, SetFocus tidies things up a bit, and PopUpMenu shows the menu.

Left clicks will work as always, allowing the user to edit the text in the text box.

CMD Software



Dealing with Null strings in Access database fields

By default Access string fields contain NULL values unless a string value (including a blank string like "") has been assigned. When you read these fields using recordsets into VB string variables, you get a runtime type-mismatch error.

The best way to deal with this problem is to use the built-in & operator to concatenate a blank string to each field as you read it. For example,

Dim DB As Database
Dim RS As Recordset
Dim sYear As String

Set DB = OpenDatabase("Biblio.mdb")
Set RS = DB.OpenRecordset("Authors")
sYear = "" & RS![Year Born]

Pradeep Arora



Display animated GIFs in VB

While the Picture ActiveX control offers a great way to display graphics, it only shows the first image in an animated GIF. To display a fully animated GIF, without rebuilding the entire graphic frame by frame, you can use the WebBrowser control (just keep in mind that this control isn't available to machines without IE 3.0 or greater). To do so, select the Microsoft Internet Controls component. When you do, the WebBrowser control appears on Visual Basic's toolbar. Drop the control onto a form, then in the form's Load() event place the following code:

 
WebBrowser1.Navigate "C:\Internet\anim.gif" 

Where the filespec points to a valid animated GIF path or URL. When you run the program, Visual Basic displays the GIF.

Unfortunately, the WebBrowser also displays a right-hand scroll bar--probably not what you want for a decorative image. Believe it or not, you can turn this scrollbar off just like you would normally via HTML, as in:

 
WebBrowser1.Navigate "about:<html><body scroll='no'><img 
src='D:\Internet\anim.gif'></img></body></html>" 

Now when you run the form, Visual Basic displays the image sans scrollbar.

ZD Tips



Drag & Drop the real selected VB treeview node

Enabling drag and drop in the treeview control can seem deceptively easy. The property sheet displays two properties for altering this capability. However, as you probably know, it takes a little code behind the scenes to actually make the drag and drop work. No doubt, you've seen the control's OLEStartDrag, OLEDragOver, and OLEDragDrop events, which you use to implement this technique. The basic idea behind a drag and drop code procedure is to set a public node variable equal to the currently selected node, highlight each node the mouse passes over during the OLEDragOver event, then add the dragged item to the node under the mouse pointer to complete the procedure. With this in mind, after first setting the control's OLEDragMode to 1-ccOLEDragAutomatic, (and filling it with items, of course) you might think to initiate the OLEStartDrag event like so:

Option Explicit
Public dragNode As Node, hilitNode As Node

Private Sub TreeView1_OLEStartDrag(Data As MSComctlLib.DataObject, _
                 AllowedEffects As Long)
    Set dragNode = Treeview1.SelectedNode
End Sub

Here, the code sets the node to be dragged equal to the currently selected node. Unfortunately, this doesn't work. That's because, a node only becomes a selected node after the MouseUp event. And as you know, you initiate a drag by holding the mouse button down. As is, the above code actually selects the previously selected node instead. So, to indicate the correct node, use the HitTest method in the treeview's MouseDown event, like so:

Private Sub TreeView1_MouseDown(Button As Integer, Shift As Integer, _
                      x As Single, y As Single)
    Set dragNode = TreeView1.HitTest(x, y)
End Sub

This event could replace the OLEStartDrag event entirely, unless of course you have further node testing that you wish to perform before starting the drag and drop procedure.

ZD Tips



Dragging items from one list to another

Here's a way that you can let users drag items from one list and drop them in another one.

Create two lists (lstDraggedItems, lstDroppedItems) and a text box (txtItem) in a form (frmTip).

Put the following code in the load event of your form.

Private Sub Form_Load()
    ' Set the visible property of txtItem to false
    txtItem.Visible = False
    'Add items to list1 (lstDraggedItems)
    lstDraggedItems.AddItem "Apple"
    lstDraggedItems.AddItem "Orange"
    lstDraggedItems.AddItem "Grape"
    lstDraggedItems.AddItem "Banana"
    lstDraggedItems.AddItem "Lemon"
    '
End Sub

In the mouseDown event of the list lstDraggedItems put the following code:

Private Sub lstDraggedItems_MouseDown(Button As Integer, Shift As Integer,
                                      X As Single, Y As Single)
    '
    txtItem.Text = lstDraggedItems.Text
    txtItem.Top = Y + lstDraggedItems.Top
    txtItem.Left = X + lstDraggedItems.Left
    txtItem.Drag
    '
End Sub

In the dragDrop event of the list lstDroppedItems put the following code:

Private Sub lstDroppedItems_DragDrop(Source As Control,
                                     X As Single, Y As Single)
    '
    If lstDraggedItems.ItemData(lstDraggedItems.ListIndex) = 9 Then
        Exit Sub
    End If
    ' To make sure that this item will not be selected again
    lstDraggedItems.ItemData(lstDraggedItems.ListIndex) = 9
    lstDroppedItems.AddItem txtItem.Text
    '
End Sub

Now you can drag items from lstDraggedItems and drop them in LstDroppedItems.

Note that you cannot drag from the second list to the first. Also, the dragged item remains in the first list. You'll have to address those limitations yourself.

Bassam Alkharashi



Easily generating random numbers

If you use random numbers to a large extent, you probably get tired of always having to put in Randomize and then the equation. This simple subroutine handles both chores for you. First, put the following routine in your project's main module.

Public Function GenRndNumber(Upper As Integer, Lower As Integer) As Integer
   Randomize
   GenRndNumber = Int((Upper - Lower + 1) * Rnd + Lower)
End Function

To get a random number between 99 and -99, just enter:

RandomNumber = GenRndNumber(99, -99)

You can get a random letter between "A" and "M" with this:

RandomLetter = Chr$(GenRndNumber(asc("M"), asc("A")))

You can eliminate the middle of a range by putting the call to GenRndNumber inside a DO Loop. The following will get a random number from 50 to 99 or -50 to -99.

'Initialize the number to be generated within the area you want excluded.
RandomNumber = 0
Do Until Abs(RandomNumber) > 49
   RandomNumber = GenRndNumber (99, -99)
Loop

Be sure to declare RandomNumber and RandomLetter as appropriate. This procedure has a minor benefit for anyone who uses a lot of calls to the random number generator. It actually generates slightly less machine code than calls to the RND function. While not generally a concern nowadays, there may be times when a programmer will need to find a way to cut down on the amount of generated machine code.

John Slaughter



Enabling/disabling all of the controls in a control array

As you may know, VB does not allow you to pass a control array as an argument to a Sub/Function. So how do you go about writing generic routines that work on control arrays? Say for example you wanted to enable/disable any set of controls on a form - here's one method...

Place this code in a module:

Public Sub EnableControls(Form As Form, ControlArray As Control, _
                          State As Boolean)

Dim ctl As Control 'Control being tested

For Each ctl In Form.Controls
   If ctl.Name = ControlArray.Name Then
      'this control is a member of the control array
      Ctl.Enabled = State
   End If
Next

End Sub

Create a form and add a control array of a few textboxes called txtSample and an array of check boxes called chkSample. Add two command buttons (cmdText & cmdCheck) and label them accordingly.

In the cmdText_Click event add the following code:

Call EnableControls(Me, txtSample(0), Not (txtSample(0).Enabled))

And add this code to the cmdCheck_Click event:

Call EnableControls(Me, chkSample(0), Not (chkSample(0).Enabled))

Run the app and click the buttons to enable/disable the attached group of controls. Once I started using this technique, I can't think how I ever coded without it.

Richard Allsebrook



Evaluate string formulas in VB

If you've come to Visual Basic from Microsoft Access, you probably miss the handy Eval() method. This method evaluates a supplied string as though it were code. As a result, you could easily evaluate mathematical expressions passed as a string, like so:

 
iResult = Eval("(2 * 3) + 5)") 

which fills iResult with the value 11.

To achieve this same functionality in Visual Basic, you've no doubt resorted to complicated string parsing functions. Or perhaps you added Access' DLL to your project, which may have seemed like an awful lot of DLL for such a simple method.

Now, though, you'll be happy to know that you can use the Eval() method without a lot of overhead. Microsoft provides this functionality in its Script Control. This very lightweight ActiveX component is available so you can add end-user scripting ability to your applications. However, as a result, it also comes with an Eval method. Adding this component to your project provides very little overhead and gives you the ability to evaluate mathematical strings.

To download this OCX, visit

http://msdn.microsoft.com/scripting/

Select the Script Control link, then follow the Download instructions. The following code shows the simple VB we added to a command button's click event. The SC1 script control evaluates a mathematical formula in the txtFormula textbox.

 
Private Sub Command1_Click() 
   MsgBox txtFormula & " = " & SC1.Eval(txtFormula) 
End Sub 

ZD Tips



Finding the CD-ROM drive's letter

The following code loops through all your computer's drive letters, to see which letter is mapped to the CD-ROM drive. It returns the drive letter if successful or an empty string on failure.

Public Function BI_GetCDROMDrive() As String
    Dim lType As Long
    Dim i As Integer
    Dim tmpDrive As String
    Dim found As Boolean

    On Error GoTo ErrorHandler

    'Loop thru A-Z. If found, exit early.
    For i = 0 To 25
        tmpDrive = Chr(65 + i) & ":\"
        lType = GetDriveType(tmpDrive)    'Win32 API function
        If (lType = DRIVE_CDROM) Then    'Win32 API constant
            found = True
            Exit For
        End If
    Next
    If Not found Then
        tmpDrive = ""
    End If

    BI_GetCDROMDrive = tmpDrive

ErrorHandler:
    Err.Description = "BI_GetCDROMDrive failed: Unexpected error."
    BI_Errorhandler

End Function

Frank May



Force VB 6.0 to open maximized code windows

While VB 5.0 was very good at remembering how you preferred the IDE windows-maximized or normal, VB 6.0 isn't. It always opens the Code and Object windows in normal view. Fortunately, you can modify this behavior with a minor tweak to the Windows Registry so that the IDE always opens these two windows maximized. Unfortunately, when you make these changes, VB 6.0 will ALWAYS open them maximized-it still won't remember your preferences between sessions.

Before we begin, take note that altering the Registry is risky business and you should always make a back-up copy of the settings so that you can restore them if something untoward happens. That said, to force VB 6.0 to open a maximized Code or Object window, you add a new value called MDIMaximized to the following Registry key:

HKEY_CURRENT_USER/Software/Microsoft/Visual Basic/6.0/MDIMaximized = "1"

To do so, in Windows click the Start button and select Run. Enter RegEdit in the Run dialog box, then click OK. When Windows displays the system registry, navigate through the keys until you've found the VB 6.0 folder. Next, right-click anywhere in the right-hand pane and select New/String Value from the shortcut menu. Enter MDIMaximized as the name and press [Enter]. Now, right-click on MDIMaximized and select Modify from the shortcut menu. Finally, in the Edit String dialog box, enter 1 for the value and click OK. When you do, Windows assigns the value to MDIMaximized. That's all there is to it! Close the registry and open a Code or Object window in a VB 6.0 project. The IDE displays them maximized.

ZD Tips



Generate accurate ADO RecordCount values in VB

As you know, the ADO RecordCount property returns the number of records in an ADO recordset. Of course, in several instances, this property also returns a -1 instead. The value RecordCount returns depends on the recordset's cursor type: -1 for a forward- only cursor; the actual count for a static or keyset cursor; and either -1 or the actual count for a dynamic cursor, depending on the data source.

You may be surprised to learn that RecordCount will be -1 for recordsets created with the Execute method from a Connection or Command object. That's because this method generates a forward- only recordset, which, as we mentioned earlier, returns -1. As an example, enter and run the following procedure in a standard VB project. When you run it, the message box displays -1 for the recordset based on myConRst, and 6246 for myKeyRst.

Sub TestRecordCount()
Dim myConn As ADODB.Connection
Dim myComm As String
Dim myConRst As ADODB.Recordset
Dim myKeyRst As ADODB.Recordset
Dim sConnection As String
    sConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=D:\Microsoft Visual Studio\VB98\Biblio.mdb"

    Set myConn = New ADODB.Connection
    Set myKeyRst = New ADODB.Recordset

    myConn.Open sConnection
    myComm = "Select * From Authors"
    Set myConRst = myConn.Execute(myComm, , adCmdText)

    myKeyRst.Open myComm, myConn, adOpenKeyset
    MsgBox "RecCount from Connection: " & myConRst.RecordCount & _
        vbCr & "From Recordset: " & myKeyRst.RecordCount

    Set myKeyRst = Nothing
    Set myConRst = Nothing
    Set myConn = Nothing
End Sub

ZD Tips



Getting sensible Win32 API call errors

Most of the Win32 API calls return extended error information when they fail. To get this information in a sensible format, you can use the GetLastError and FormatMessage APIs.

Add the following declarations and function to a BAS module in a VB project:

Option Explicit

Public Declare Function GetLastError Lib "kernel32" () As Long
Public Declare Function FormatMessage Lib "kernel32" Alias "FormatMessageA" _
 (ByVal dwFlags As Long, lpSource As Any, ByVal dwMessageId As _
  Long, ByVal dwLanguageId As Long, ByVal lpBuffer As String, ByVal nSize _
  As Long, Arguments As Long) As Long

Public Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000

Public Function LastSystemError() As String
    '
    ' better system error
    '
    Dim sError As String * 500
    Dim lErrNum As Long
    Dim lErrMsg As Long
    '
    lErrNum = GetLastError
    lErrMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, _
      ByVal 0&, lErrNum, 0, sError, Len(sError), 0)
    LastSystemError = Trim(sError)
    '
End Function

Now place a command button on a standard VB form and call the LastSystemError function:

Private Sub Command1_Click()
    '
    MsgBox LastSystemError
    '
End Sub

If there was no error registered, you'll see a message saying "The operation completed successfully."

When using this function, keep these points in mind:

1. Many API calls reset the value of GetLastError when successful, so the function must be called immediately after the API call that failed.

2. The last error value is kept on a per-thread basis, therefore the function must be called from the same thread as the API call that failed.

Duncan Jones



Getting the backslashes right when using App.Path

In order to retrieve the current directory path for an application's executable, the App.Path property can be used. Be aware, however, of a major gotcha when doing that. If your application is executing in the root directory, a backslash will be added to the end of the directory. If your application is executing in a sub-directory, then the result will not have a backslash on the end. Using the following function will clear up this problem:

Public Function AppPath(sFileName As String) As String
  If Right$(App.Path, 1) = "\" Then
    AppPath = App.Path & sFileName
  Else
    AppPath = App.Path & "\" & sFileName
  End If
End Function

For example, AppPath("test.txt") will append the filename correctly no matter what directory the EXE resides in.

Scott B. Lewis



Globally replace text using just 5 lines of VB code

If you've ever tried to implement a text search and replace function in Visual Basic, you probably resorted to lengthy parsing loops that used Instr() and an entire host of string functions. If so, you'll be glad to know that there's a much easier way.

With the advent of VBScript 5.0, Microsoft introduced the Regular Expression engine, which you can also use in Visual Basic. If you've used Perl or JavaScript, you may be familiar with these pattern-matching powerhouses. In a nutshell, regular expressions let you define a pattern, literal or representative, which you can then match against a second string. For example, the literal pattern 'abc' as a regular expression would find a match in 'dabcef', 'abcdef', or 'defabc'. To further refine a regular expression, the Regular Expression engine offers a host of special metacharacters, similar to wildcard characters. For example, using these metacharacters, you could search for any word that began with a letter and ended in a digit. To view these metacharacters and their uses, visit

http://msdn.microsoft.com/scripting/default.htm

then search in the VBScript documentation for the RegExp object's Pattern property.

To create a simple find and replace subroutine, first download the VBScript DLL from

http://www.microsoft.com/msdownload/vbscript/scripting.asp

Once you've registered the dll on your system, in your VB project set a reference to Microsoft VBScript Regular Expressions. Now, via code, create a RegExp object, like so

Set MyReg = new RegExp

Next, set the object's properties.

MyReg.IgnoreCase = True
MyReg.Global = True
MyReg.Pattern = "abc"

Here, we've used a literal string to create a global, case- insensitive pattern. Finally, you execute the object's Replace method on the target string, as in

txtSearchText = MyReg.Replace(txtSearchText, "def")

That's *all* there is to it! This example would replace "abc" with "def" wherever it occurred within txtSeartText. To execute the replace only on the first matching string, set the Global property to False.

ZD Tips



Hide the mouse cursor in a Visual Basic application

The ShowCursor API function provides a quick way to hide the mouse cursor. It takes the following declaration statement

 
Private Declare Function ShowCursor Lib "user32" (ByVal bShow As Long) As Long 

When you enter 0 for the bShow argument, the mouse cursor disappears. Enter -1 to make the mouse reappear. Just be aware when you use this function, however, that just because you hide the mouse pointer, doesn't mean it's disabled. To see what we mean, add the above declaration and the following code to a form.

 
Dim blnShow As Boolean 

Private Sub Form_Click() 
   blnShow = Not blnShow 
   ShowCursor blnShow 
End Sub 

Private Sub Form_Load() 
   blnShow = True 
End Sub 

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer) 
   ShowCursor True 
End Sub 

Now, run the project. When you click the form, Visual Basic toggles off the cursor's visibility. Click the form once more, and the mouse cursor reappears. If the mouse cursor had been truly disabled, you wouldn't have been able to make the mouse cursor reappear because the click wouldn't have registered. Now, click the form so the mouse cursor disappears, and move the mouse towards the IDE's menu bar. As the invisible mouse cursor passes over each enabled button, it raises, ready to be clicked. In fact, you can still use the mouse to choose options from the menu bar, or click on screen objects.

ZD Tips



Implementing a "wait" function in VB

Here's a simple way of implementing an accurate "wait" function in VB.

1) Add a timer called timer1 to a form. Set its Interval property to 0 and it's enabled property to FALSE.

2) Add two labels (label1 and label2) and a command button (command1) to the form.

3) Add the following subroutine and Timer1 event code:

Public Sub Wait(seconds)

    '-- Turn timer on
    Timer1.Enabled = True

    '-- Set Timer Interval
    Me.Timer1.Interval = 1000 * seconds
    While Me.Timer1.Interval > 0
        DoEvents
    Wend

    '-- Turn timer off
    Timer1.Enabled = False
End Sub

Private Sub Timer1_Timer()
    Timer1.Interval = 0
End Sub

That's it! Use the Wait function anywhere a delay is required, for example:

Private Sub Command1_Click()
    Label1.Caption = Now
    Wait (5)
    Label2.Caption = Now
End Sub

Peter Arnold



Importing Registry settings

You can use just a few lines of code to import Registry settings. If you have an application called myapp.exe and a Registry file called myapp.reg, the following code will put those settings into the Registry without bothering the user.

Dim strFile As String
strFile = App.Path & "\" & opts.AppExeName & ".reg"
If Len(Dir$(strFile)) > 1 Then
    lngRet = Shell("Regedit.exe /s " & strFile, vbNormalFocus)
End If

Dan Newsome



Increase Your RAD-ness by creating your own VB5 Templates

You can create your own VB Templates quickly and easily. If you find that you are adding the same routines to your forms, classes, BAS modules, etc. you can build generic versions and place them in the Templates folder tree of VB5. By placing the coding module (frm, bas.cls, etc.) in the proper subfolder of the Templates folder, you'll see the new item appear whenever you select the Add... dialog box. You can add as many controls, library references, and lines of code as you wish to the templates.

CAUTION: If you uninstall VB5, you may loose your Templates folder and all its contents. Be sure to keep a secured copy of all your template files in a safe location.

Michael C. Amundsen



Increment and decrement dates with the [+] and [-] keys

If you've ever used Quicken, you've probably notice a handy little feature in that program's date fields. You can press the [+] key to increment one day, [-] to decrement one day, [PgUp] to increment one month, and [PgDn] to decrement one month. In this tip, we'll show you how to emulate this behavior with Visual Basic.

First, insert a text box on a form (txtDate). Set its text property to "" and its Locked property to TRUE.

Now place the following code in the KeyDown event:

Private Sub txtDate_KeyDown(KeyCode As Integer, Shift As Integer)
    '
    ' 107 = "+" KeyPad
    ' 109 = "-" KeyPad
    ' 187 = "+" (Actually this is the "=" key, same as "+" w/o the shift)
    ' 189 = "-"
    ' 33 = PgUp
    ' 34 = PgDn
    '
    Dim strYear As String
    Dim strMonth As String
    Dim strDay As String
    '
    If txtDate.Text = "" Then
        txtDate.Text = Format(Now, "m/d/yyyy")
        Exit Sub
    End If
    '
    strYear = Format(txtDate.Text, "yyyy")
    strMonth = Format(txtDate.Text, "mm")
    strDay = Format(txtDate.Text, "dd")
    '
    Select Case KeyCode
        Case 107, 187 ' add a day
          txtDate.Text = Format(DateSerial(strYear, strMonth, strDay) + 1, _
                                  "m/d/yyyy")
        Case 109, 189 ' subtract a day
          txtDate.Text = Format(DateSerial(strYear, strMonth, strDay) - 1, _
                                  "m/d/yyyy")
        Case 33 ' add a month
          txtDate.Text = Format(DateSerial(strYear, strMonth + 1, strDay), _
                                  "m/d/yyyy")
        Case 34 ' subtract a month
          txtDate.Text = Format(DateSerial(strYear, strMonth - 1, strDay), _
                                  "m/d/yyyy")
    End Select
    '
End Sub

The one nasty thing about this is that if you have characters that are not the characters usually in a date (i.e., 1-9, Monday, Tuesday, or /) you get errors in the format command. To overcome this, I set the Locked property to True. This way, the user can't actually type a character in the field, but the KeyDown event still fires.

Mike Coleman



Keep your background processes running

In Visual Basic, if you make a call to the MSGBOX function, all other background processes that you may have running (counters, timer events, etc) are stopped until the user acknowledges the Msgbox dialog box. This can be potentially devastating if you write an application that runs unattended.

To overcome this problem, you must use the Windows API call for the MessageBox function. It looks and acts the same as the VB "msgbox" function, but does not stop the background processes from running.

In a module, paste the following API declaration:

Declare Function MessageBox Lib "user32" Alias "MessageBoxA" (ByVal hwnd As _
  Long, ByVal lpText As String, ByVal lpCaption As String, ByVal wType As _
  Long) As Long

Next, on the default form add a timer control, 2 command buttons, and a label. Then type the following code into the form, which demonstrates the VB msgbox and API MessageBox functions. That's all there is to it.

Private Sub Command1_Click()
    MsgBox "The Timer STOPS!"
End Sub

Private Sub Command2_Click()
    MessageBox Me.hwnd, "Notice the timer does not stop!", "API Call", _
      vbOKOnly + vbExclamation
End Sub

Private Sub Timer1_Timer()
    Label1.Caption = Time
End Sub

Marshall Brown



Keeping Track of VB Source Code Builds

Keeping track of source code builds in VB can be easy, if you use the Version Numbering feature provided in EXE creations. Click on the options button when creating an EXE file. Then turn on the "Auto Increment" checkbox. Versioning supports a three-segment version number: Major, Minor, and Revision. The Auto Increment feature, if selected, will automatically increase the Revision number by one each time you run the Make Project command for this project. Typically, build information will go on an About form. Just add a label called "lblVersion" and add the following code to your form:

lblVersion.Caption = "Version: " & App.Major & "." & _
     App.Minor & "." & App.Revision

If my Major version number is 2, the Minor number is 1 and the Revision is 12, the label will display: "Version: 2.1.12"

Inside Visual Basic



Labeling your forms

Do you have a ton of screens in your application? Do you also have plenty of users who want to "help you" by pointing out buttons that are one twip out of place? Sometimes it's hard to know what screen users are talking about when they're trying to communicate a problem--particularly if they're in a different location than you.

To reduce the pain of this process, I add a label (called lblHeader) to the top of each GUI window, nominally to hold start-up information for users when they first open the window. You can also use this label to hold the name of the window the user is looking at, by using the following code:

Private Sub Form_Load()
    SetupScreen me
End Sub

Public SetupScreen (frm as Form)
    ' Do other set-up stuff here (fonts, colors).
    HookInFormName frm
End Sub

Public Sub HookInFormName(frm As Form)
    ' The Resume Next on Error allows forms that do not use a standard
    ' header label to get past this.
    On Error Resume Next
    frm.lblHeader.Caption = "(" & frm.Name & ") " & frm.lblHeader.Caption
End Sub

Note that if you don't want to use a label, that you can also use code like

frm.print frm.name

to print to the back of the window itself.

Bill Shadish



Launch your default web browser from a VB app

Here is a nice function to launch the default web browser from a VB application.

Add the following code to the general section of a module:

Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
(ByVal hwnd As Long, ByVal lpOperation As String, _
ByVal lpFile As String, ByVal lpParameters As String, _
ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long

Public Const SW_SHOWNORMAL As Long = 1
Public Const SW_SHOWMAXIMIZED As Long = 3
Public Const SW_SHOWDEFAULT As Long = 10

Add the following code to a form, which spawns off the default browser:

Public Sub RunBrowser(strURL As String, iWindowStyle As Integer)
Dim lSuccess As Long

'-- Shell to default browser
lSuccess = ShellExecute(Me.hwnd, "Open", strURL, 0&, 0&, iWindowStyle)
End Sub

To launch the VBCE.com web site, use the following function call:

Call RunBrowser ("www.vbce.com", SW_SHOWNORMAL)

Scott B. Lewis



Let DateDiff() determine if two dates are in the same month

To determine if two dates are in the same month, your first instinct may be to simply use the Month() function on each date, then compare the two resulting numbers. However, under these circumstances, the comparison would equate 1/1/2000 with 1/1/1999. Instead, the DateDiff() function provides one quick way to make this determination, like so:

DateDiff("m", Date1, Date2)

In this expression, the DateDiff() function finds the difference in calendar months between the two dates. If the expression returns a zero, then the two dates are in the same calendar month. To include this feature in an conditional expression, you could use the following in a query:

If DateDiff("m", Date1, Date2) Then
   'Month's are different
Else
   'Month's are same
End If

ZD Tips



Let VB determine if the CD ROM drive contains media

To quickly determine if the CD ROM drive contains media, use the Scripting Runtime library's IsReady property for the Drive object. For CD ROM drives, this property returns True only if the drive contains the appropriate media. To take advantage of this handy property, add a Reference to Microsoft Scripting Runtime library (scrrun.dll). Next, create a Drive variable based on the CD ROM drive, and test the IsReady property, as shown below:

Dim FSO As FileSystemObject
Dim CDDrive As Drive

Set FSO = New FileSystemObject
Set CDDrive = FSO.GetDrive("E:")
If CDDrive.IsReady Then
     MsgBox CDDrive.VolumeName
Else
     MsgBox "Please enter a CD."
End If

Set CDDrive = Nothing
Set FSO = Nothing

ZD Tips



Limiting the field length for unbound combo boxes

Sometimes you may wish to limit the number of characters to be entered into unbound combo boxes. As combo boxes do not have the MaxLength property like a text box, the limit must be checked programmatically. The combo box KeyPress event works fine.

Private Sub cmbLimitedText_KeyPress(KeyAscii As Integer)

Const iMAXCHARS As Integer = 25
Const iBACKSPACEKEY As Integer = 8

'Make sure max characters not exceeded
If Len(cmbLimitedText.Text) >= iMAXCHARS Then

   'if not backspace then set to zero
   If KeyAscii <> iBACKSPACEKEY Then
      'Set to zero to cancel keypress
      KeyAscii = 0
   End If

End If

End Sub

This does not affect tabbing/cursor key/delete functions within the combo box.

ZD Journals' Inside Visual Basic journal



Maintain a Visual Basic DBGrid's runtime column widths

The DBGrid is a great way to display data as a familiar grid-style output. However, if you change the column widths on the grid at runtime, Visual Basic doesn't use these new widths the next time you run the application. Fortunately, the following procedure will maintain the column widths for you:

Sub DBGridLayout(Operation As String)
'save width of columns
Dim lWidth As Long
Dim clm As Column
Dim lDefWidth As Long

 lDefWidth = DBGrid1.DefColWidth
 For Each clm In DBGrid1.Columns
   With clm
     Select Case LCase(Operation)
       Case "save"
         lWidth = .Width
         SaveSetting App.Title, "Cols", CStr(.ColIndex), lWidth
       Case "load"
         lWidth = GetSetting(App.Title, "Cols", CStr(.ColIndex), lDefWidth)
         .Width = lWidth
     End Select
   End With
 Next clm
End Sub

As you can see, this procedure uses the SaveSetting and GetSetting functions to store the current width values in VB's portion of the registry. To use the procedure, call it from the parent form's Load and Unload events. Then, indicate which operation you want the procedure to perform, as in:

Private Sub Form_Load()
   DBGridLayout "Load"
End Sub

Private Sub Form_Unload(Cancel As Integer)
   DBGridLayout "Save"
End Sub

Rolf Brandt



Manipulate your controls from the keyboard

If you're not comfortable using your mouse--or can't achieve the precise results you'd like--these tips will come in handy.

First, you can resize controls at design time by using the [Shift] and arrow keys, as follows:

     SHIFT + RIGHT ARROW increases the width of the control
     SHIFT + LEFT ARROW decreases the width of the control
     SHIFT + DOWN ARROW increases the height of the control
     SHIFT + UP ARROW decreases the height of the control

Note: The target control must have focus, so click on the control before manipulating it from the keyboard.

Second, by using the [Control] key and the arrow keys, you can move your controls at design time, as follows:

     CONTROL + RIGHT ARROW to move the control to the right
     CONTROL + LEFT ARROW to move the control to the left
     CONTROL + DOWN ARROW to move the control downwards
     CONTROL + UP ARROW to move the control upwards

If you select more than one control (by clicking on the first and shift-clicking on the others), the above procedures will affect all the selected controls.

Narayana Vyas Kondreddi



Measuring a text extent

It's very simple to determine the extent of a string in VB. You can do so with WinAPI functions, but there's an easier way: Use the AutoSize property of a Label component. First, insert a label on a form (labMeasure) and set its AutoSize property to True and Visible property to False. Then write this simple routine:

Private Function TextExtent(txt as String) as Integer
   labMeasure.Caption = txt
   TextExtent = labMeasure.Width
End Function

When you want to find out the extent of some text, simply call this function with the string as a parameter.

In my case it turned out that the measure was too short. I just added some blanks to the string. For example:

Private Function TextExtent(txt As String) As Integer
   labMeasure.Caption = "  " & txt
   TextExtent = labMeasure.Width
End Function

Nenad Cus Babic



Opening a browser to your homepage

You can use code like the following to open a browser to your homepage. Modify filenames, paths, and URLs as necessary to match the values on your system.

Dim FileName As String, Dummy As String
Dim BrowserExec As String * 255
Dim RetVal As Long
Dim FileNumber As Integer
Const SW_SHOWNORMAL = 1 ' Restores Window if Minimized

Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
(ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, _
ByVal lpParameters As String, ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As Long

Declare Function FindExecutable Lib "shell32.dll" Alias "FindExecutableA" _
(ByVal lpFile As String, ByVal lpDirectory As String, ByVal lpResult As _
String) As Long

  '<Code> ---------

  BrowserExec = Space(255)
  FileName = "C:\TEMPHTM.HTM"

  FileNumber = FreeFile()     ' Get unused file number

  Open FileName For Output As #FileNumber  ' Create temp HTML file
    Write #FileNumber, "<HTML> <\HTML>"  ' Output text
  Close #FileNumber   ' Close file

  'Then find the application associated with it.
  RetVal = FindExecutable(FileName, Dummy, BrowserExec)
  BrowserExec = Trim$(BrowserExec)
  'If an application is found, launch it!
  If RetVal <= 32 Or IsEmpty(BrowserExec) Then ' Error
    Msgbox "Could not find a browser"
  Else
    RetVal = ShellExecute(frmMain.hwnd, "open", BrowserExec, _
      "www.myurl.com", Dummy, SW_SHOWNORMAL)
    If RetVal <= 32 Then        ' Error
      Msgbox "Web Page not Opened"
    End If
  End If

Kill FileName  ' delete temp HTML file

Dan Newsome



Prevent drag and drop operations on VB treeview root nodes

Often when you allow drag and drop operations in a treeview control, you won't want a root node dragged to another level. For instance, if you filled the control with departments and their employees, you wouldn't want a department node placed under an employee. To prevent this from happening, you can use the OLEStartDrag event to provide validation. For example, testing for the Parent property provides a quick way determine a root node. As you know, the Parent property returns an item's Parent node. So, if the property returns nothing, then the target node to be dragged is really a root node. Using our previous tip for assigning a treeview's current node, you can use code like the following:

Option Explicit
Public dragNode As Node, hilitNode As Node

Private Sub TreeView1_MouseDown(Button As Integer, Shift As Integer, _
                 x As Single, y As Single)
    Set dragNode = TreeView1.HitTest(x, y)
End Sub

Private Sub TreeView1_OLEStartDrag(Data As MSComctlLib.DataObject, _
                 AllowedEffects As Long)
    If dragNode.Parent Is Nothing Then Set dragNode = Nothing
End Sub

Then in the OLEDragOver and OLEDragDrop events, create conditional statements to test if the dragNode is nothing, then perform the drag and drop operations accordingly. For example, the OLEDragOver event might look like this:

Private Sub TreeView1_OLEDragOver(Data As MSComctlLib.DataObject, _
     Effect As Long, Button As Integer, Shift As Integer, _
     x As Single, y As Single, State As Integer)

    If Not dragNode Is Nothing Then
        TreeView1.DropHighlight = TreeView1.HitTest(x, y)
    End If
End Sub

ZD Tips



Preventing Add-Ins from loading at launch

When you launch Visual Basic 4 or 5, any active Add-Ins also launch. If there's an error in one of the Add-Ins, however, you could encounter a global protection fault.

To prevent this from happening, you can turn off Add-Ins before launching VB. To do so, launch Notepad or WordPad and open the file VBAddin.INI in your Windows directory. You'll see a series of entries like this:

AppWizard.Wizard=1

Just change the "1" to a "0" in each entry. Then save the file and launch VB. The program will launch without any Add-Ins.

Of course, to add and remove Add-Ins while you're in Visual Basic, just choose Add-In Manager from the Add-Ins menu.

Deborah Kurata



Preventing multiple instances of VB apps from Inside Visual Basic

You can easily prevent users from running multiple instances of your programs by taking advantage of the PrevInstance property of the App object.

To do so, enter the following code in your application's opening form:

If App.PrevInstance Then
   MsgBox ("Cannot load program again."), vbExclamation, _
           "The requested application is already open"
   Unload me
End If

This technique will also prevent multiple users from accessing single-user applications.

The Cobb Group



Quick Custom Dialogs for DBGrid Cells

It's easy to add custom input dialogs to al the cells in the Microsoft Data Bound Grid control.

First, add a DBGrid control and Data control to your form. Next, set the DatabaseName and RecordSource properties of the data control to a valid database and table ("biblio.mdb" and "Publishers" for example). Then set the DataSource property of the DBGrid control to Data1 (the data control).

Now add the following code to your form.

' general declaration area
Dim strDBGridCell As String

Private Sub DBGrid1_AfterColEdit(ByVal ColIndex As Integer)
    '
    DBGrid1.Columns(ColIndex) = strDBGridCell
    '
End Sub

Private Sub DBGrid1_BeforeColEdit(ByVal ColIndex As Integer, ByVal KeyAscii
As Integer, Cancel As Integer)
    '
    strDBGridCell = InputBox("Edit DBGrid Cell:", ,
DBGrid1.Columns(ColIndex))
    '
End Sub

Now whenever you attempt to edit any cell in the DBGrid, you'll see the InputBox prompt you for input. You can replace the InputBox with any other custom dialog you wish to build.

Mike Amundsen



Quick Text Select On GotFocus

When working with data entry controls, the current value in the control often needs to be selected when the control received focus. This allows the user to immediately begin typing over any previous value. Here's a quick subroutine to do just that:

Public Sub FocusMe(ctlName As Control)
    '
    With ctlName
        .SelStart = 0
        .SelLength = Len(ctlName)
    End With
    '
End Sub

Now add a call to this subroutine in the GotFocus event of the input controls:

Private Sub txtFocusMe_GotFocus()
    Call FocusMe(txtFocusMe)
End Sub

Scott



Quickly switching an object's Enabled property

You can easily switch an object's Enabled property with a single line of code:

optSwitch.enabled = abs(optSwitch.enabled) - 1

Here's how the technique works: When Enabled is True, its numeric value is -1. The absolute value of -1 is 1, so subtracting 1 from 1 would yield 0, which is False. When Enabled is False, its numeric value is 0; 0 - 1 would then yield -1, or True.

This technique is an enhancement of the common usage

fraOption.enabled = optSwitch.enabled

to have an object follow the value of any other object's Enabled property.

Editor's Note: This technique depends on VB's definition of True and False. To make this technique less dependent on that definition, you can use the following code:

OptSwitch.enabled = NOT OptSwitch.enabled

This code works for any Boolean data type.

Kevin Brown



Read Registry values in VB without API

Often you'll want to manipulate the Windows registry in Visual Basic without resorting to lengthy API calls. Fortunately you can with the Registry Access Functions (RegObj.dll) library. This DLL lets you create a Registry object with which you can manipulate specific keys. For example, in a previous tip we showed you how to use the WebBrowser control to display an animated GIF.

Unfortunately, the WebBrowser is only available if the target machine also has Internet Explorer installed. As a result, you may want to display an alternative image if Internet Explorer isn't available. To determine if IE is installed on the target Windows 95 machine, first set a reference in your project to Registry Access Functions. Then use code similar to that below:

 
Dim myReg As New Registry, KeyFound As Boolean 
Dim HasIE As Boolean, sValue As String 
   sValue = "" 
   KeyFound = myReg.GetKeyValue(HKEY_LOCAL_MACHINE, _ 
      "Software\Microsoft\Windows\CurrentVersion\" & _ 
      "App Paths\IEXPLORE.EXE", "Path", sValue) 
   If KeyFound Then HasIE = (sValue <> "") 
   If HasIE Then MsgBox sValue 'contains the path to IE 

   Set myReg = Nothing 

ZD Tips



Referring to a DE Command's recordset in VB 6.0

Each Command object in Visual Basic 6.0's new Data Environment also has an associated recordset. In code, you refer to this object by preceding the Command object's name with rs. So, for example, if your Data Environment contains a Command named cmdSomeQuery, then to refer to the resulting recordset, you'd use something like this:

 
Set rst = DataEnvironment1.rscmdSomeQuery 

ZD Tips



Replace lengthy path strings with VB object variables

Many methods that manipulate a drive, folder, or file in the MS Scripting Runtime library require a filespec argument--the item's path, represented as a string. However, because the Path property is the default property for these three items, you can also use object variables in place of the path strings. So for instance, to copy one folder to another using object variables, you could use

fldr1.Copy fldr2, False

or

MyFSO.CopyFile file1, file2, False

ZD Tips



Retrieve a file's short name in VB without API calls

Many times, you'll need to reference a file by it's 8.3 file naming convention. Chances are, you've seen these file names in MSDOS. For instance, under this convention, the Program Files folder becomes Progra~1. You'll be happy to know that you can retrieve this short path name without resorting to the GetShortPathName API function. As an alternative, the new Scripting Runtime library offers the ShortPath property, which it provides for both File and Folder objects. To obtain a file's short path name, simply add a project Reference to the Microsoft Scripting Runtime, then use code similar to:

Private Sub Form_Load()
Dim fsoFile As File, fso As FileSystemObject

    Set fso = New FileSystemObject
    Set fsoFile = fso.GetFile("C:\MyReallyLongName.txt")
    MsgBox fsoFile.ShortPath
    Set fsoFile = Nothing
    Set fso = Nothing
End Sub

ZD Tips



Retrieve subitem values from a VB ListView control

When you use the ListView control in Report view, the control shows a table-like list. It's the same view that appears in the file dialog boxes (Open, Save, etc.) when you click the Details button. Each row displays additional subitem information about the main item. For example, in the file dialog boxes, the main item is the file itself, while the Size and Type columns are the subitems. When you use the ListView control in your own applications, VB makes it easy to determine which item you've selected. When you select an item in Report view, VB triggers the ItemClick event, which passes a ListItem object (the item you just clicked). From this object, you can determine it's value. So, for example, the entire event procedure might look like this

Private Sub lstvwFiles_ItemClick(ByVal Item As MSComctlLib.ListItem)
   strFileSelected = Item
End Sub

This statement would pass the selected item's value (Value being the object's default property) into the strFileSelected variable. However, you may have wondered how to retrieve the Item's associated subitem values. Fortunately, VB provides the ListSubItems collection, which contains all the subitems of an individual item. So, continuing with the example above, the Size and Type items are both subitems of the main File item. To ascertain the values of these subitems, you simply loop through the collection, like so

Private Sub lstvwFiles_ItemClick (ByVal Item As MSComctlLib.ListItem)
Dim itm As ListSubItem
Dim strSubs As String

   MsgBox "ItemClicked: " & Item
   For Each itm In Item.ListSubItems
      strSubs = strSubs & itm & ": "
   Next itm
   MsgBox "SubItems: " & strSubs
End Sub

ZD Tips



Run a VB application during start-up

Depending on the version of Windows you wish to target, you have two different ways to run a VB application during the boot process. Under Windows 9x systems, place a Shell command in the [Boot] section of the System.ini file, such as

Shell=Myprog.exe

For Windows NT/2000, enter the same shell command in the Registry, under

HKEY_CURRENT_USER\Software\Microsoft\WindowsNT\CurrentVersion\Winlogon

Exercise caution when exercising these options, however, as modifications may prevent Windows from displaying properly.

ZD Tips



Scroll to a VB listview control's selected item

As you know, the listview control provides a great way to associate items with icons, pictures, or in report rows. This control makes it easy to programmatically select an item in the list. However, just because Visual Basic selects an item, doesn't always mean that it will be visible to a user. For example, if you have many items displayed in the control, you might need to use the scroll bar to display an item at the bottom of the list. Fortunately, the listview control also makes it easy to programmatically scroll to a selected item. Each item in the list has an EnsureVisible method. Just as you'd expect, when you call this method, it forces the control to display the item in the visible portion of the listview.

To illustrate, drop a listview control onto a default form, then right-click on it and select Properties from the shortcut menu. In the Properties Pages dialog box, change the control's View property to 3 - lvwReport. Next, click on the Column Headers tab, click Insert Column, and enter "Which Foo?" in the Text field. Click OK to complete the process.

Finally, add the following code to the form:

 
Private Sub Form_Load() 
Dim x As Integer 
  With ListView1 
    For x = 1 To 20 
      .ListItems.Add Key:="foo" & x, Text:="foo" & x 
    Next x 
    .SelectedItem = .ListItems("foo20") 
    .SelectedItem.EnsureVisible 
  End With 
End Sub 

When you run the project, Visual Basic opens the form and displays the 20th item in this list

ZD Tips



Selecting all text when a TextBox gets focus

When you present the user with default text in a TextBox, you'll often want to select that text when the TextBox gets focus. That way, the user can easily type over your default text.

The function below will do the trick. The first click on the TextBox will select all the text; the second click will place the cursor.

Public Sub TextSelected()
Dim i As Integer
Dim oMyTextBox As Object

Set oMyTextBox = Screen.ActiveControl
    If TypeName(oMyTextBox) = "TextBox" Then
        i = Len(oMyTextBox.Text)
        oMyTextBox.SelStart = 0
        oMyTextBox.SelLength = i
    End If
End Sub

Just add the function to your project and call it from the TextBox's GotFocus event.

Private Sub Text1_GotFocus()
    TextSelected
End Sub

Christopher Buteau



Setting the TabIndex property of every control on a form

If you're like me, you hate having to go back and set the TabIndex property on every control on a form every time the placement of controls change. You can use the following subroutine to set the TabIndex property of every control on a form from top to bottom, left to right. This comes in especially handy on complex data entry forms with many labels, text boxes, and command buttons.

Enter the following code in a form:

Private Sub Form_Load()
  Call SetTabOrder(Me)
End Sub

Sub SetTabOrder(frm As Form)
  Dim FormControl, CurrTop, CurrLeft, ctlControl

  '-- Loop through all controls on the form
  For ctlControl = 0 To frm.Count
    CurrTop = 0  'set starting top
    CurrLeft = 0 'set starting left

    '-- Check every control on the form
    For Each FormControl In frm.Controls
      '-- Check if the control hasn't already been set
      If FormControl.Tag <> True Then
        '-- If current control's TOP property is greater than
        '-- Start TOP, set new comparison value
        If FormControl.Top > CurrTop Then
          CurrTop = FormControl.Top
          CurrLeft = FormControl.Left
          '-- Control's top property is equal
        ElseIf FormControl.Top = CurrTop Then
          '-- If current control's LEFT property is greater than
          '-- Start LEFT, set new comparison value
          If FormControl.Left > CurrLeft Then
            CurrTop = FormControl.Top
            CurrLeft = FormControl.Left
          End If
        End If
      End If
    Next FormControl

    '-- Check every control on the form if the top and left of
    '-- the control match the top and left of largest control
    For Each FormControl In frm.Controls
      If FormControl.Top = CurrTop And _
        FormControl.Left = CurrLeft Then
        '-- set it's tabindex to 0 which will move all previously
        '-- set tabindexes to 1 larger than they are now
        FormControl.TabIndex = 0
        '-- Do not process again
        FormControl.Tag = True
      End If
    Next FormControl
  Next
End Sub

Timothy H. Fielder



Showing long ListBox entries as a ToolTip

Sometimes the data you want to display in a list is too long for the size of ListBox you can use. When this happens, you can use some simple code to display the ListBox entries as ToolTips when the mouse passes over the ListBox.

First, start a new VB project and add a ListBox to the default form. Then declare the SendMessage API call and the constant (LB_ITEMFROMPOINT) needed for the operation:

Option Explicit

'Declare the API function call.
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
  (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
    lParam As Any) As Long

' Add API constant
Private Const LB_ITEMFROMPOINT = &H1A9

Next, add some code to the form load event to fill the ListBox with data:

Private Sub Form_Load()
    '
    ' load some items in the list box
    With List1
        .AddItem "Michael Clifford Amundsen"
        .AddItem "Walter P.K. Smithworthy, III"
        .AddItem "Alicia May Sue McPherson-Pennington"
    End With
    '
End Sub

Finally, in the MouseMove event of the ListBox, put the following code:

Private Sub List1_MouseMove(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
    '
    ' present related tip message
    '
    Dim lXPoint As Long
    Dim lYPoint As Long
    Dim lIndex As Long
    '
    If Button = 0 Then ' if no button was pressed
        lXPoint = CLng(X / Screen.TwipsPerPixelX)
        lYPoint = CLng(Y / Screen.TwipsPerPixelY)
        '
        With List1
            ' get selected item from list
            lIndex = SendMessage(.hwnd, _
              LB_ITEMFROMPOINT, _
              0, _
              ByVal ((lYPoint * 65536) + lXPoint))
            ' show tip or clear last one
            If (lIndex >= 0) And (lIndex <= .ListCount) Then
                .ToolTipText = .List(lIndex)
            Else
                .ToolTipText = ""
            End If
        End With '(List1)
    End If '(button=0)
    '
End Sub

Matt Vandenbush



Simple file checking from anywhere

To keep your applications running smoothly, you may need to check that certain files exist. Here's a simple routine to make sure they do:

Public Sub VerifyFile(FileName As String)
    '
    On Error Resume Next
    'Open a specified existing file
    Open FileName For Input As #1
    'Error handler generates error message with file and exits the routine
    If Err Then
        MsgBox ("The file " & FileName & " cannot be found.")
        Exit Sub
    End If
    Close #1
    '
End Sub

Now add a button to your form and place the code below behind the "Click" event.

Private Sub cmdVerify_Click()
    '
    Call VerifyFile("MyFile.txt")
    '
End Sub

Inside Visual Basic



Simple input validation

Here's a way to achieve validation in text boxes and other controls that support the KeyPress event. It's simple, but functional.

First, add this function to your project:

Function ValiText(KeyIn As Integer, ValidateString As String, _
                  Editable As Boolean) As Integer

    Dim ValidateList As String
    Dim KeyOut As Integer
    '
    If Editable = True Then
         ValidateList = UCase(ValidateString) & Chr(8)
    Else
         ValidateList = UCase(ValidateString)
    End If
    '
    If InStr(1, ValidateList, UCase(Chr(KeyIn)), 1) > 0 Then
        KeyOut = KeyIn
    Else
        KeyOut = 0
        Beep
    End If
    '
    ValiText = KeyOut
    '
End Function

Then, for each control whose input you wish to validate, just put something like this in the KeyPress event of the control:

    KeyAscii=ValiText(Keyascii, "0123456789/-",True)

Doing so will filter out any undesired keys that go to the control, accepting only the keys defined by the second parameter. In this case, that parameter ("0123456789/-") defines characters that are valid for a date.

The function's third parameter controls whether the [Backspace] key can be used.

Note that this implementation of the function ignores the case of the incoming keys, so if your second parameter were "abcdefg", the function would also allow "ABCDEFG" to be entered.

Juan Lozano



Simplying the addition of items to ComboBoxes

I often need to add items to a ComboBox and store an index or ID value in the ItemData property. I've found that the code needed to add items to the ComboBox and to check the ItemData property of the currently selected item looks clumsy. So, I've written two simple helper routines to clean the code up a bit. Here they are:

'---------------------------------------------------------------------
'   AddComboItem
'   AddComboItem
'---------------------------------------------------------------------
Public Sub AddComboItem(cboAdd As ComboBox, _
                        ByVal sText As String, ByVal lData As Long)

    cboAdd.AddItem sText
    cboAdd.ItemData(cboAdd.NewIndex) = lData

End Sub

'---------------------------------------------------------------------
'   CurrComboData
'   CurrComboData
'---------------------------------------------------------------------
Public Function CurrComboData(cbo As ComboBox) As Long

    If cbo.ListIndex <> -1 Then
       CurrComboData = cbo.ItemData(cbo.ListIndex)
    Else
       CurrComboData = -1
    End If

End Function

Now, instead of writing

    cboTest.AddItem "Hello"
    cboTest.ItemData(cboTest.NewIndex) = 5

you can just write

    AddComboItem cboTest, "Hello", 5

Instead of writing

    ID = cboTest.ItemData(cboTest.ListIndex)

you can write

    ID = CurrComboData(cboTest)

As an added bonus, CurrComboData protects you from the runtime error generated if ListIndex is -1. Just be sure to check for a return of -1 from CurrComboData.

Brent Langdon



Sorting numbered items in a Listbox

Consider the following items/files: FILE1.BMP, FILE2.BMP, FILE3.BMP, and FILE10.BMP

If you place them in a sorted List or Combo control, they come up listed like this:

FILE1.BMP
FILE10.BMP
FILE2.BMP
FILE3.BMP

But what you really want is this:

FILE1.BMP
FILE2.BMP
FILE3.BMP
FILE10.BMP

This is how you do it. After filling your list control, call the ReSort routine, passing the original offending list control as the only parameter:

Sub ReSort(L As Control)
Dim P%, PP%, C%, Pre$, S$, V&, NewPos%, CheckIt%
Dim TempL$, TempItemData&, S1$

For P = 0 To L.ListCount - 1
  S = L.List(P)
  For C = 1 To Len(S)
    V = Val(Mid$(S, C))
    If V > 0 Then Exit For
  Next
  If V > 0 Then
    If C > 1 Then Pre = Left$(S, C - 1)
    NewPos = -1
    For PP = P + 1 To L.ListCount - 1
      CheckIt = False
      S1 = L.List(PP)
      If Pre <> "" Then
        If InStr(S1, Pre) = 1 Then CheckIt = True
      Else
        If Val(S1) > 0 Then CheckIt = True
      End If
      If CheckIt Then
        If Val(Mid$(S1, C)) < V Then NewPos = PP
      Else
        Exit For
      End If
    Next
    If NewPos > -1 Then
      TempL = L.List(P)
      TempItemData = L.ItemData(P)
      L.RemoveItem (P)
      L.AddItem TempL, NewPos
      L.ItemData(L.NewIndex) = TempItemData
      P = P - 1
    End If
  End If
Next
End Sub

ZD Journals' Inside Visual Basic journal



Specifying maximum lengths in a ComboBox

The ComboBox control doesn't have a MaxLength property like a TextBox does. You can add some code to emulate this property, however. Just add the following code to the KeyPress event of your ComboBox:

Private Sub Combo1_KeyPress(KeyAscii As Integer)
    'If the user is trying to type the eleventh key and...
    ' ...this key is not the Backspace Key, cancel the event!
    Const MAXLENGTH = 10
    If Len(Combo1.Text) >= MAXLENGTH And KeyAscii <> vbKeyBack Then
       KeyAscii = 0
    End If
    '
End Sub

You can change the MaxLength value to any number you want. As you can see, the code allows the user to use the [Backspace] key; you could enable other keys by simply adding their KeyAscii values the way we did with [Backspace].

Roberto Giacometti Machado



Spell check RTF documents in VB

To spell check RTF documents, you could resort to embedding Word into your application. However, if you plan to use the WebBrowser control, there's no need to program Word for spell checking. That's because the WebBrowser control provides this functionality for you. To access it, you use the WebBrowser control's ExecWB() method with the OLECMDID_SPELL flag. To illustrate, add the WebBrowser control to a form, then add the following code to the form's Load() event

 
WebBrowser1.Navigate "D:\Internet\spell.rtf" 

Replace the path with any string that points to an RTF file. Next, add a command button, and enter this code in its Click() event:

 
WebBrowser1.ExecWB OLECMDID_SPELL, OLECMDEXECOPT_DODEFAULT 

Run the project. The WebBrowser displays the RTF file, and when you click the command button, VB runs through the normal spell check operation.

ZD Tips



Sure-Fire Way to Allow Users to Cancel Form Unloads

There are a number of different ways a user can unload a form. Click the Exit button or menu item; click on the X in the upper-right corner of the form; select Close from the form window's pop-up menu in the upper-left corner; even cancel the program from the task manager or reboot the machine. The best way to give your users the power to cancel a form-unload activity, whatever its source, is to place all your form-unload checking code in the QueryUnload event of the form. This event fires no matter which method is used to unload your form.

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    '
    ' *** universal unload check
    '
    Dim strQuestion As String
    Dim intAnswer As Integer
    Dim aryMode As Variant
    '
    aryMode = Array("vbFormControlMenu", "vbFormCode", "vbAppWindows", _
                    "vbAppTaskManager", "vbFormMDIForm")
    '
    strQuestion = "Ready to unload this form?"
    '
    intAnswer = MsgBox(strQuestion, vbQuestion + vbYesNo, _
                       aryMode(UnloadMode))
    If intAnswer = vbNo Then
        Cancel = -1
    End If
    '
End Sub

Mike Amundsen



The VB Printer object's paper bin selection protocol

The Printer object is a handy way to create quick, simple reports without cluttering your application with one of the heavy-duty report writers. However, when you use this object to select a paper bin, it must correspond to a paper size. For example, suppose your printer's upper bin contains legal size (11x14) paper and the lower bin contains letter size (8.5x11) paper. When you set the PrinterPaperBin to vbPRBNUpper, you must also change the Printer.PaperSize property to vbPRPSLegal. Otherwise, Visual Basic ignores the PaperBin setting, and the paper will come out of the lower bin.

ZD Tips



Tip for finding a leap year

Here is a nice function for finding a leap year. The trick is to not only dividing the year by 4, but also test for division by 100 and 400.

Public Function IsLeapYear(iYear As Integer)
    '-- Check for leap year
    If (iYear Mod 4 = 0) And _
      ((iYear Mod 100 <> 0) Or (iYear Mod 400 = 0)) Then
        IsLeapYear = True
    Else
        IsLeapYear = False
    End If
End Function

Tricia L. Lewis



Update the VB 6.0 DataGrid control with recordset changes

The DataGrid control is a great way to display multiple data rows in a table-like format. Unfortunately, the control is also plagued with bugs. Some have been fixed by Service Patch 3, but some haven't. For instance, if you connect the DataGrid to a DataEnvironment, then make changes to the underlying recordset and refresh the DataGrid with the Refresh method, the control still doesn't reflect the changes. Unfortunately, the Refresh method doesn't work when the control's DataSource is a DataEnvironment. Instead, to show the updated recordset changes, first update the DataEnvironment's recordset, then rebind the DataGrid to the DataEnvironment. So, if you have a Refresh button, it's click event might look like this:

DataEnvironment1.rsCommand1.Requery
Set DataGrid1.DataSource = DataEnvironment1

Now, when you click the Refresh button, the code rebinds the DataEnvironment to the DataGrid and refills the control with the refreshed data.

ZD Tips



Use a VB listbox's ItemData property to store ID numbers

Often, when you fill a listbox with data from a database, you want the control to display descriptive values; but you may also need it to store key values as well. For example, if you fill a listbox with employee names, no doubt you'll also want to store each employee's ID number. Fortunately, if you use numbers as an ID, Visual Basic makes it easy to so with listbox's ItemData property. This property lets you store an additional number for each item in the list. To see how this works, add a listbox to a standard form and attach the following code. When you run the project, the listbox displays only the employee names. Click an item to see a message box that shows both the selected employee name as well as the stored ID number.

Private Sub Form_Load()
   ' Fill List1 and ItemData array with corresponding items in sorted order.
   With List1
      .AddItem "Mallik Murthy"
      .ItemData(List1.NewIndex) = 42310
      .AddItem "Chien Lieu"
      .ItemData(List1.NewIndex) = 52855
      .AddItem "Mauro Sorrento"
      .ItemData(List1.NewIndex) = 64932
      .AddItem "Cynthia Bennet"
      .ItemData(List1.NewIndex) = 39227
   End With
End Sub

Private Sub List1_Click()
Dim Msg As String
   ' Append the employee number and the employee name.
   With List1
      Msg = .ItemData(.ListIndex) & " " & .List(.ListIndex)
      MsgBox Msg
   End With
End Sub

Mallik Murthy



Use FileDSNs to ease ODBC Installs

If you're using an ODBC connection to your database, you can ease the process of installing the application on workstations by using the FileDSN (data source name) instead of the more-common UserDSN. You define your ODBC connection as you normally would with UserDSNs. However, the resulting definition is not stored in the workstation registry. Instead it gets stored in a text file with the name of the DSN followed by ".dsn" (i.e. "MyFileDSN.dsn"). The default folder for all FileDSNs is "c:\program files\common files\Odbc\data sources". Now, when you want to install the VB application that uses the FileDSN, all you need to do is add the FileDSN to the Install package and run the install as usual. No more setting up DSNs manually!

NOTE: FileDSNs are available with ODBC 3.0 and higher.

Michael C. Amundsen



Use FreeFile to Prevent File Open Conflicts

Both Access and VB let you hard code the file numbers when using the File Open statement. For example:

     Open "myfile.txt" for Append as #1
     Print #1,"a line of text"
     Close #1

The problem with this method of coding is that you never know which file numbers may be in use somewhere else in your program. If you attempt to use a file number already occupied, you'll get a file error. To prevent this problem, you should always use the FreeFile function. This function will return the next available file number for your use. For example:

     IntFile=FreeFile()
     Open "myfile.txt" for Append as #intFile
     Print #intFile,"a line of text"
     Close #intFile

Inside Visual Basic



Use ParamArray to Accept an Arbitrary Number of Parameters

You can use the ParamArray keyword in the declaration line of a method to create a subroutine or function that accepts an arbitrary number of parameters at runtime. For example, you can create a method that will fill a list box with some number of items even if you do not know the number of items you will be sent. Add the method below to a form:

Public Sub FillList(ListControl As ListBox, ParamArray Items())
    '
    Dim i As Variant
    '
    With ListControl
        .Clear
        For Each i In Items
            .AddItem i
        Next
    End With
    '
End Sub

Note that the ParamArray keyword comes BEFORE the parameter in the declaration line. Now add a list box to your form and a command button. Add the code below in the "Click" event of the command button.

Private Sub Command1_Click()
    '
    FillList List1, "TiffanyT", "MikeS", "RochesterNY"
    '
End Sub

Michael C. Amundsen



Use the DEFAULT and CANCEL Properties of a Command Button

If you set a command button's Default property to TRUE any time the user presses the [ENTER] key, that button will execute its Click event code.

If you set a command button's Cancel property to TRUE any time the user presses the [ESCAPE] key, that button will execute its Click event.

Only one button on the form can have its Default or Cancel property set to TRUE. Also, the same button can't have both Default and Cancel set to TRUE.

Mike Amundsen



Use the VB6 Split function to count substrings

As we mentioned in a previous tip, VB6 introduced the Split function, which provides a new way to parse strings. With it, you indicate a delimiter within a string, and VB fills a one- dimensional array with the substrings.

However, in addition to the functionality described above, you can also use the Split function as a quick way to determine the number of substrings within a larger string. For example, suppose you want to determine the number of times 'sea' appears in the string "She sells seashells by the seashore." To do so, simply perform the split, and use the UBound function to count the number of elements in the resultant array, as in

strTungTied = "She sells seashells by the seashore."
arySea = Split(strTungTied, "sea")
MsgBox "'Sea' appears in '" & strTungTied & "' " & UBound(arySea) & "times."

When you run this code snippet, Visual Basic informs you that 'sea' appears in the phrase twice.

John Fiala



Use VB's ADOX to determine if internal database objects exist

Soon after ADO's release, Microsoft created an extension to the object library called ActiveX Data Objects Extensions, or ADOX. This library includes most of the DAO features missing from standard ADO, including objects related to a database's schema. With ADOX you can easily determine if a database contains a specific table, view, or query.

To do so, set a reference to Microsoft ADO Ext. for DDL and Security. For schema purposes, the ADOX consists of a Catalog object, which contains the object collections that describe the database, tables and views among them. To test for an existing table in the Tables collection, you can either iterate through the collection and check each item's name against the test string; or you can use our favorite method, as seen in the following code:

 
Private Sub Command1_Click() 
Dim cat As ADOX.Catalog 
Dim tbl As ADOX.Table 

    Set cat = New ADOX.Catalog 
    cat.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.3.51;" _ 
        & "Data Source=C:\Program Files\Microsoft Visual Studio\VB98\Biblio.mdb" 
    On Error Resume Next 
    Set tbl = cat.Tables("MyTable")    'try "Authors" too 

    If tbl Is Nothing Then 
        MsgBox "MyTable doesn't exist" 
    Else 
        MsgBox "MyTable exists" 
        Set tbl = Nothing 
    End If 

    Set cat = Nothing 
    Set tbl = Nothing 
End Sub 

ZD Tips



Use VB's RegExp object to validate email address syntax

Nowadays, if your application requires a user to enter specific company information, you probably have a field for an email address. No doubt, you'll want to ensure that the address not only contains the @ and dot, but that the remaining characters contain only letters, numerals, or underscores (and perhaps a dash or period). At first, this may seem like a daunting task. And if you use standard Visual Basic string functions alone, it will be. Fortunately, the VB RegExp object provides an easier way.

If you didn't read our previous tip on using a RegExp object for global find and replace operations, then to use regular expressions in Visual Basic you'll need to download the VBScript 5.0 DLL from

http://www.microsoft.com/msdownload/vbscript/scripting.asp

Once you install VBScript's DLL, the Microsoft VBScript Regular Expressions option appears in Visual Basic's References dialog box. Add this reference to your project, and you'll be free to use the RegExp object. The following code validates an email address in a textbox named Text1:

 
Dim myReg As RegExp 

Private Sub Form_Load() 
   Set myReg = New RegExp 
   myReg.IgnoreCase = True 
   myReg.Pattern = "^[\w-\.]+@\w+\.\w+$" 
End Sub 

Private Sub Text1_Validate(Cancel As Boolean) 
   Cancel = Not myReg.Test(Text1) 
End Sub 

Here, the pattern accepts any number of numeric, underscore, letters, periods, or dash characters before the @ character and only numerals, underscores, or letters before and after the dot.

ZD Tips



Using GetRows to Quickly Save Data Fields to Memory Variables

If you need to copy information from database fields into memory variables, you can do it quickly using the GetRows method of the Recordset object. The GetRows method copies one or more rows of data directly into a Variant data type and stores the information as a two-dimensional array in the formvarData(Field,Column).

To test the GetRow method, add a button to a VB form and paste the following code into the Click event of the button. Be sure to fix the reference to location of the BIBLIO.MDB database in the OpenDatabase method. Also be sure to set up a reference to the Microsoft DAO 3.5 Object Library.

Private Sub cmdGetDataRow_Click()
    '
    ' show getrow method
    '
    Dim ws As Workspace
    Dim db As Database
    Dim rs As Recordset
    '
    Dim varDataRows As Variant
    Dim intRows As Integer
    Dim intColumns As Integer
    '
    Dim intLoopRow As Integer
    Dim intLoopCol As Integer
    Dim strMsg As String
    '
    Set ws = DBEngine.CreateWorkspace(App.EXEName, "admin", "")
    Set db = ws.OpenDatabase("e:\devstudio\vb\biblio.mdb")
    Set rs = db.OpenRecordset("SELECT * FROM Authors")
    '
    intRows = InputBox("How Many Rows?", "GetRows Example", 0)
    intColumns = rs.Fields.Count
    varDataRows = rs.GetRows(intRows)
    '
    For intLoopRow = 0 To intRows - 1
        strMsg = ""
        For intLoopCol = 0 To intColumns - 1
            strMsg = strMsg & varDataRows(intLoopCol, intLoopRow) & vbCrLf
        Next
        MsgBox strMsg
    Next
    '
    rs.Close
    db.Close
    ws.Close
    '
End Sub

Michael C. Amundsen



Using the Alias Option to Prevent API Crashes

A number of Windows APIs have parameters that can be more than one data type. For example, the WinHelp API call can accept the last parameter as a Long or String data type depending on the service requested.

Visual Basic allows you to declare this data type as "Any" in the API call, but this can lead to type mismatch errors or even system crashes if the value is not the proper form.

You can prevent the errors and improve the run-time type checking by declaring multiple versions of the same API function in your program. By adding a function declaration for each possible parameter type, you can continue to use strong data type checking.

To illustrate this technique, add the following APIs and constants to a Visual Basic form. Notice that the two API declarations differ only in their initial name ("WinHelp" and "WinHelpSearch") and the type declaration of the last parameter ("dwData as Long" and "dwData as String").

' WinHelp APIs
Private Declare Function WinHelp Lib "user32" Alias "WinHelpA" (ByVal hwnd
As Long, ByVal lpHelpFile As String, ByVal wCommand As Long, ByVal dwData
As Long) As Long
Private Declare Function WinHelpSearch Lib "user32" Alias "WinHelpA" (ByVal
hwnd As Long, ByVal lpHelpFile As String, ByVal wCommand As Long, ByVal
dwData As String) As Long
'
Private Const HELP_PARTIALKEY = &H105&
Private Const HELP_HELPONHELP = &H4
Private Const HelpFile = "c:\program files\devstudio\vb5\help\vb5.hlp"

Now add two command buttons to your form (cmdHelpAbout and cmdHelpSearch) and place the following code behind the buttons. Be sure to edit the location of the help file to match your installation of Visual Basic.

Private Sub cmdHelpAbout_Click()
    '
    WinHelp Me.hwnd, HelpFile, HELP_HELPONHELP, &H0
    '
End Sub

Private Sub cmdHelpSearch_Click()
    '
    WinHelpSearch Me.hwnd, HelpFile, HELP_PARTIALKEY, "option"
    '
End Sub

When you press on the HelpAbout button, you'll see help about using the help system. When you press on the HelpSearch button, you'll see a list of help entries on the "option" topic.

Mike Amundsen



Using the Base Address for In-Process Components to speed up the loading phase

When you create an in-process component and load it in your Visual Basic application, the process loads at a base address in memory.

How can you change the Base Address of your In-Process Component? To enter the base address for your component, open the Project Properties dialog box and select the Compile tab. The address is entered in the DLL Base Address box as an unsigned decimal or hexadecimal integer. The default value is &H11000000 (285,212,672). If you neglect to change this value, your component will conflict with every other in-process component compiled using the default. Staying well away from this address is recommended.

Choose a base address between 16 megabytes (16,777,216 or &H1000000) and two gigabytes (2,147,483,648 or &H80000000). The base address must be a multiple of 64K. The memory used by your component begins at the initial base address and is the size of the compiled file, rounded up to the next multiple of 64K.

Your program cannot extend above two gigabytes, so the maximum base address is actually two gigabytes minus the memory used by your component. Executables will usually load at the 4 megabyte logical address. The region below 4 megabytes is reserved under Windows 95, and regions above two gigabytes are reserved by both Windows 95 and Windows NT.

A good way to make sure all your components have a different base address is to keep track of all them, and create a random selective tool to create new unique base addresses. This way your components will not conflict with each other.

Richard King



Using the Date type with an ADO source

The first thought to handle a date in VB is to use a Date type variable. In fact, this will not work for Null dates coming from an ADO source. The reason is that the date internal type has a different behavior than the adDate ADO type.

To see the differences between Date and adDate take a look at the following code (under VB6 with Microsoft ActiveX Data Objects Recordset 2.0 Library, but the same would happen with Microsoft ActiveX Data Objects 2.0 Library):

Private Sub Date_handling()
Dim lDate As Date
Dim lDateVar As Variant
Dim lRs As ADOR.Recordset

'Initialization:
Set lRs = New ADOR.Recordset
lRs.Fields.Append "MyDate", adDate, , adFldIsNullable
lRs.Open
lRs.AddNew
lRs!MyDate = Date
MsgBox lRs!MyDate

'Storing:
lDate = lRs!MyDate
lDateVar = lRs!MyDate

'Setting:
'lDate = Null 'This would not work
lDateVar = Null 'This will work

lRs!MyDate = lDateVar
'lRs!Update
lRs.Close
End Sub

A String is not more appropriate: a Null string does not represent a Null date. Unfortunately, the default setting in VB6's DataEnvironment puts a TextBox on the form when you drag and drop a date field.

To have a good internal representation of the date, you should use a Variant.

Henry Cesbron Lavau



Using the DEFAULT property with multiple-line text boxes

Don't use the DEFAULT property on a data entry form on which you have a multiple-line text box. If you do, the user will not be able to hit enter to make "hard returns" in their text, because that will automatically click the default button rather than moving to a new line in the text box. Instead, provide an access or "accelerator" key to the button. See the VB help on the Caption property for details on how to do this.

Jeff Lyons



Using the Win32 API to write to the NT EventLog

Recently a tip went out that showed you how to write to the NT EventLog using the App object. This method has 2 limitations:

1) You cannot use the code during a debug session. 2) The source entry in the Event Log is always VBRuntime

Using the Win32 API alleviates these problems. Enter the following code in the General Declarations of a module:

    Declare Function RegisterEventSource Lib "advapi32.dll" Alias _
        "RegisterEventSourceA" (ByVal lpUNCServerName As String, _
        ByVal lpSourceName As String) As Long

    Declare Function DeregisterEventSource Lib "advapi32.dll" ( _
        ByVal hEventLog As Long) As Long

    Declare Function ReportEvent Lib "advapi32.dll" Alias _
        "ReportEventA" (ByVal hEventLog As Long, ByVal wType As Integer, _
        ByVal wCategory As Integer, ByVal dwEventID As Long, _
        ByVal lpUserSid As Any, ByVal wNumStrings As Integer, _
        ByVal dwDataSize As Long, plpStrings As Long, _
        lpRawData As Any) As Boolean

    Declare Function GetLastError Lib "kernel32" () As Long

    Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
        hpvDest As Any, hpvSource As Any, _
        ByVal cbCopy As Long)

    Declare Function GlobalAlloc Lib "kernel32" ( _
         ByVal wFlags As Long, _
         ByVal dwBytes As Long) As Long

    Declare Function GlobalFree Lib "kernel32" ( _
         ByVal hMem As Long) As Long

    '-- Public Constants
    Public Const EVENTLOG_SUCCESS = 0
    Public Const EVENTLOG_ERROR_TYPE = 1
    Public Const EVENTLOG_WARNING_TYPE = 2
    Public Const EVENTLOG_INFORMATION_TYPE = 4
    Public Const EVENTLOG_AUDIT_SUCCESS = 8
    Public Const EVENTLOG_AUDIT_FAILURE = 10

Public Function WriteToEventLog(sMessage As String, sSource As String, _
                    iLogType As Integer, vEventID As Integer) As Boolean

    Dim bRC              As Boolean
    Dim iNumStrings      As Integer
    Dim hEventLog        As Long
    Dim hMsgs            As Long
    Dim cbStringSize     As Long
    Dim iEventID         As Integer

    hEventLog = RegisterEventSource("", sSource)
    cbStringSize = Len(sMessage) + 1
    hMsgs = GlobalAlloc(&H40, cbStringSize)
    CopyMemory ByVal hMsgs, ByVal sMessage, cbStringSize
    iNumStrings = 1

    '-- ReportEvent returns 0 if failed,
    '-- Any other number indicates success
    If ReportEvent(hEventLog, _
       iLogType, 0, _
       iEventID, 0&, _
       iNumStrings, cbStringSize, _
       hMsgs, hMsgs) = 0 Then
        '-- Failed
        WriteToEventLog = False
    Else
        '-- Sucessful
        WriteToEventLog = True
    End If

    Call GlobalFree(hMsgs)
    DeregisterEventSource (hEventLog)
End Function

An example of how to write to the NT EventLog:

Call WriteToEventLog("Warning, file exceeded recommended limit.", _
     "Test App", EVENTLOG_WARNING_TYPE, 1003)

Scott Lewis



Using WithEvents to add features to a textbox

Have you ever felt that certain standard Windows controls were lacking features? Would you like to add features to a textbox which you wouldn't have to code every time you created a new textbox? Typically, you would create a generic subroutine and call it from the event of every control. With VB5 and higher, a command named "WihEvents" provides a simpler solution.

Let's say you wanted a textbox that would only accept uppercase letters as input. If lowercase letters were entered they would be automatically converted to uppercase. Numbers and all symbols would be rejected. And whenever the mouse pointer passed over it, you wanted to show the mouse position in the textbox. Finally, let's say you didn't want the fourth textbox to accept the letter 'Z'.

Create a standard EXE project, add four textbox controls onto the default form, and add a class module. Enter the following code to Form1:

'General Declarations
Private clsTextBox1 As Class1
Private clsTextBox2 As Class1
Private clsTextBox3 As Class1
Private clsTextBox4 As Class1

Private Sub Form_Load()
  Set clsTextBox1 = New Class1
  Set clsTextBox1.TextBoxCtl = Text1
  Set clsTextBox2 = New Class1
  Set clsTextBox2.TextBoxCtl = Text2
  Set clsTextBox3 = New Class1
  Set clsTextBox3.TextBoxCtl = Text3
  Set clsTextBox4 = New Class1
  Set clsTextBox4.TextBoxCtl = Text4
End Sub

Private Sub Text4_KeyPress(KeyAscii As Integer)
   '-- This code executes before the class keypress
   If KeyAscii = Asc("a") Then Beep
End Sub

In Class1, enter the following code:

Private WithEvents txt As TextBox

Public Property Set TextBoxCtl(OutsideTextBox As TextBox)
  Set txt = OutsideTextBox
End Property

Private Sub txt_KeyPress(KeyAscii As Integer)
  '-- Convert to Uppercase
  KeyAscii = Asc(UCase$(Chr$(KeyAscii)))
End Sub

Private Sub txt_MouseMove(Button As Integer, Shift As Integer, X As
Single, Y As Single)
  txt.ToolTipText = "X:" & X & " Y:" & Y
End Sub

To add new functionality to all four textboxes, simply enter code into the class. Remember, the keypress event for textbox4 executes first when a key is pressed. Then, the Classes keypress event fires next.

Todd Bleeker



Writing to the Windows NT event log

Windows applications typically write to the NT event log to provide the user with useful information. In VB5/6, the App object now provides methods to make writing to the event log in Windows NT a snap:

'-- Start Event Logging
Call App.StartLogging("", vbLogToNT)

'-- Log Events to NT
Call App.LogEvent("Info", vbLogEventTypeInformation)
Call App.LogEvent("Error", vbLogEventTypeError)
Call App.LogEvent("Warning", vbLogEventTypeWarning)

Be aware though, these functions will only work in the compiled EXE. They will be ignored in design mode. Check out the Microsoft knowledge base article Q161306 for more information.

Scott Lewis