101 Tech Tips For VB Developers 008
101 Tech Tips For VB Developers 008
101 Tech Tips For VB Developers 008
For VB Developers
When mbDisabled is set to True, the changes made by the user to the listbox selections are immediately reversed. It will appear as if the selections havent changed at all, and the list is still scrollable.
Ian Champ, received by e-mail
What happens if SQL command conventions (field name delimiters) change? Because a compile doesnt reveal such name misspellings or convention flaws, code in obscure procedures can be in production before defects are detected. Our group established a table and field dictionary in a module used for a recent large project. This helped us ensure that we correctly pasted table and field names into all SQL commands. It also provided a repository that simplified maintenance. As database resource names changed or new name-delimiting conventions were required, we revised the dictionary before recompiling. We also used the dictionary to convey descriptive information about tables and fields to developers. Our dictionary looks like this:
'tables: Public Const tblUsers As String = "[imaging users]" 'data fields: Public Const fldFirstName As String = "[first name]" '16 characters Public Const fldLastName As String = "[last name]" '16 characters Public Const fldLinePreferences As String = _ "[line preferences]" '20 characters Public Const fldUserCode As String = "[user code]" '10 characters
VB5, VB6
Level: Beginning
Programmers dont have to know the actual names of database components. They always use the constants that refer to the database components. A clean compile ensures youll use correct names and name-delimiting conventions in your SQL statements.
Doug Hagy, Greensburg, Pennsylvania Supplement to Visual Basic Programmers Journal FEBRUARY 1999 1
http://www.devx.com
1 01 TECH TIPS
For VB Developers
VB5, VB6
Level: Intermediate
Although the shaker sort improves the bubble sort, it still executes as an n-squared algorithm. However, because most programmers can code a bubble sort with their eyes closed, this is a nice way to shave 25 to 33 percent off the required execution time without having to dig out the algorithm books. Still, you dont want to use either a bubble or shaker sort for extremely large data sets.
Tan Shing Ho, Kuala Lumpur, West Malaysia 2 FEBRUARY 1999 Supplement to Visual Basic Programmers Journal
http://www.devx.com
1 01 TECH TIPS
For VB Developers
RunDialog = &H52 ' Asc("R") StartMenu = &H5B ' Asc("[") StandbyMode = &H5E ' Asc("^") -- Win98 only! End Enum Public Sub SystemAction(VkAction As SystemKeyShortcuts) Const VK_LWIN = &H5B Const KEYEVENTF_KEYUP = &H2 Call keybd_event(VK_LWIN, 0, 0, 0) Call keybd_event(VkAction, 0, 0, 0) Call keybd_event(VK_LWIN, 0, KEYEVENTF_KEYUP, 0) End Sub
variable in the Conditional Compilation Arguments fields in the Make tab of the Project properties dialog, then remember to remove it before shipping the executable. Using the Debug.Assert command is an easier way to have statements executed only while debugging and not when running a compiled program. For example, this line displays a message box only when running the program in the design environment and not in a compiled program:
Debug.Assert MsgBox("Begin Form_Load")
VB5, VB6
Level: Beginning
This happens because Debug.Assert works only in the design environment. To evaluate the assertion, VB executes the statement. However, when you compile the program, the compiler removes this line from the executable.
Jose Mojica, Davie, Florida
NIX THE X
Sometimes, you want to show a form that you dont want users to be able to cancel by clicking on the Xit might not make sense for your app. The best VB solution is to cancel the unload in the forms QueryUnload event. However, this allows users to do something wrong, for which you then have to handle and scold them. If you do nothing, it looks as if the form has a bug and wont cancel. Add this routine to a standard BAS module:
Private Declare Function GetSystemMenu Lib "user32" _ (ByVal hWnd As Long, ByVal bRevert As Long) As Long Private Declare Function RemoveMenu Lib "user32" _ (ByVal hMenu As Long, ByVal nPosition As Long, _ ByVal wFlags As Long) As Long Private Const MF_BYPOSITION = &H400& Public Sub RemoveCancelMenuItem(frm As Form) Dim hSysMenu As Long 'get the system menu for this form hSysMenu = GetSystemMenu(frm.hWnd, 0) 'remove the close item Call RemoveMenu(hSysMenu, 6, MF_BYPOSITION) 'remove the separator that was over the close item Call RemoveMenu(hSysMenu, 5, MF_BYPOSITION) End Sub
I then create a property wrapper for the control array. The wrapper takes in an enumerated constant that represents the index number and returns the control in the array:
Property Get Fields(ByVal FieldNum As FieldConstants) _ As TextBox Set Fields = txtFields(FieldNum) End Property
The advantage to this wrapper is that when you type the property name, Fields, VB prompts you with the constant names listed in the enumerated type. This way, you can refer to controls in the array by name, and you never have to look up constant names again. Also, it makes the code more legible:
Private Sub Form_Load() Fields(LastName).Text = "Mojica" End Sub
VB5, VB6
Level: Beginning
After this call, the Close menu item in the system menu and the option to cancel [X] will be disabled. Note that if youre doing other things with the system menu, you might have to adjust the position number in the RemoveMenu call.
Josh Frank, Parsippany, New Jersey
In VB, you can do the same thing, but you need to declare the
http://www.devx.com Supplement to Visual Basic Programmers Journal FEBRUARY 1999 3
1 01 TECH TIPS
For VB Developers
Private Declare Function QueryPerformanceCounter Lib _ "kernel32" (lpPerformanceCount As LARGE_INTEGER) _ As Long Private Declare Function QueryPerformanceFrequency Lib _ "kernel32" (lpFrequency As LARGE_INTEGER) As Long Private m_TicksPerSecond As Double Private m_LI0 As LARGE_INTEGER Private m_LI1 As LARGE_INTEGER Friend Sub Class_Initialize() Dim LI As LARGE_INTEGER If QueryPerformanceFrequency(LI) <> 0 Then m_TicksPerSecond = LI2Double(LI) Else m_TicksPerSecond = -1 End If End Sub Friend Property Get Resolution() As Double Resolution = 1# / m_TicksPerSecond End Property Friend Sub EnterBlock() QueryPerformanceCounter m_LI0 End Sub Friend Sub ExitBlock() QueryPerformanceCounter m_LI1 End Sub Friend Property Get ElapsedTime() As Double Dim EnterTime As Double, ExitTime As Double EnterTime = LI2Double(m_LI0) / m_TicksPerSecond ExitTime = LI2Double(m_LI1) / m_TicksPerSecond ElapsedTime = ExitTime - EnterTime End Property Friend Function LI2Double(LI As LARGE_INTEGER) As Double Dim Low As Double Const TWO_32 = 4# * 1024# * 1024# * 1024# Low = LI.LowPart If Low < 0 Then Low = Low + TWO_32 'Now Low is in the range 0...2^32-1 LI2Double = LI.HighPart * TWO_32 + Low End Function
To test, pass your credit card number, excluding the last digit, as a string parameter. The result should be the last digit of your credit card number.
Arnel J. Domingo, Hong Kong
Believe it or not, you can time even native-compiled code division. For more information, look at the MSDN Library description of the kernel APIs used here. On x86 architectures, resolution is better than 1 microsecond. Be careful, however, of trusting single instance timings, as youll find the resolution of this performance counter varies over time. In fact, the overhead of simply calling QueryPerformanceCounter in VB is quite a measurable time period itself. Although you can time single operations, youre still better off averaging the time required for hundreds or thousands of similar operations.
Alessandro Coppo, Rapallo, Italy
http://www.devx.com
1 01 TECH TIPS
For VB Developers
hWnd As Long, ByVal nIDEvent As Long, ByVal uElapse _ As Long, ByVal lpTimerFunc As Long) As Long Private Declare Function KillTimer Lib "user32" (ByVal _ hWnd As Long, ByVal nIDEvent As Long) As Long Private m_cb As Object Public Function timerSet(lTime As Long, cb As Object) _ As Long Set m_cb = cb timerSet = SetTimer(0, 0, lTime, AddressOf _ timerProcOnce) End Function Private Sub timerProcOnce(ByVal lHwnd As Long, ByVal _ lMsg As Long, ByVal lTimerID As Long, ByVal lTime _ As Long) On Error Resume Next Call KillTimer(0, lTimerID) m_cb.cbTimer End Sub
After 10 milliseconds, the code triggers the cbTimer method in the class module:
Public Sub cbTimer() ' Do some stuff End Sub
You can also use the function on forms instead of the intrinsic Timer control.
Bo Larsson, Copenhagen, Denmark
In the Form_Paint event, put this code where you wish to draw the rectangle:
Private Sub Form_Paint() Static Tmp As RECT Static TmpL As Long TmpL = GetClientRect(hWnd, Tmp) TmpL = DrawEdge(hDC, Tmp, EDGE_SUNKEN, BF_RECT) End Sub
If the rectangle doesnt draw, do a Debug.Print on the TmpL variable. It should read a nonzero value upon success.
Jeff Shimano, Mississauga, Ontario, Canada
VB5, VB6
Level: Intermediate
The statement RmDir C:\dummy causes error 75 because this directory is locked and cannot be removed. To work around this problem, check for the existence of a file by trying to open it for sequential/random/binary (anything should work) access and close it immediately afterwards. If this file exists, your routine will proceed with its code, where you
http://www.devx.com
1 01 TECH TIPS
For VB Developers
can kill the file and remove the directory. A trappable error 53, File not found, indicates the file does not exist. After you trap the error, you can redirect the execution of your code as required. This code is a good example to start with:
Private Sub Command1_Click() Dim FHandle As Long Dim FileNAme As String FileNAme = "C:\dummy\bla-bla.txt" FH = FreeFile On Error Goto ErrHadler Open FileNAme For Input As FHandle Close FHandle Kill FileNAme RmDir "C:\dummy" CleanUp: Exit Sub ErrHadler: Select case Err Case 53 'File not found Resume CleanUp Case Else 'display error info End Select End Sub
pect youd say many, which means youve had to set the Enabled property of both the buttons and the menus, depending on the availability of certain features, and youve had to keep the status of the buttons and menus in sync in many places in your code. This job can be tedious if conditions dictating the Enabled status continuously change in your application. Ill show you how to cut your code in half. Youve probably noticed that if a main menu has submenus, the user can use the main menu only to open a list of menu items. However, the main menus Click event procedure is available to you, the programmer. This procedure fires every time the user opens the main menu, always before the user has a chance to select a menu item. Use this event to check your Command buttons Enabled property that you set in other procedures of your application, then set the Enabled property of the corresponding menu items to the same value. This way, by the time the user sees the menu item list, all menu items Enabled property is set to the appropriate value. For example, if you have a menu structure similar to this, you can code your Edit menu Click event procedure: Edit Undo Cut Copy Paste Delete Use this code in your Edit menu Click event procedure:
Private Sub mnuEdit_Click mnuEditUndo.Enabled = cmdUndo.Enabled mnuEditCut.Enabled = cmdCut.Enabled mnuEditCopy.Enabled = cmdCopy.Enabled mnuEditPaste.Enabled = cmdPaste.Enabled mnuEditDelete.Enabled = cmdDelete.Enabled End Sub
You cant use Sequential Access for Output or Append. If the file does not exist, it is created automatically when the Open statement executes, and all your code loses sense. Also note that the RmDir statement causes error 75, Path/ File access error, if you try to remove a directory thats not empty. Kill all the files one by one prior to removing a directory, or opt to use an API to do the job. Of course, you would never want to go to this extreme unless you find that theres no alternative. This is an incredibly bizarre behavior, and most apps would never be affected by it.
Brian Hunter, Brooklyn, New York
VB5
Level: Intermediate
No matter how many times you change the Enabled property of buttons, menu items will always be kept in sync.
Serge Rodkopf, Brooklyn, New York
VB5, VB6
Level: Intermediate
Setting the first value sets the seed number for all subsequent items in the list, each one incrementing by one. (Microsoft recommends starting at 512 plus 1 above vbObjectError.) Now you wont have to remember error numbers throughout your code. Simply raise your errors like this:
Err.Raise Errors.InvalidUserID, "Login", "Invalid UserID"
http://www.devx.com
1 01 TECH TIPS
For VB Developers
When you type the enumeration name Errors, VB pops up a list of available choices. Be careful, though, not to add new items in the middle of the list, because the value of all entries below the new item increases by one. This can cause the parent of the object to handle errors incorrectly because the error numbers will be different. You can avoid this by specifying exactly what value you want for each Enum, instead of relying on the default increment.
Gregory Alekel, Portland, Oregon
You should now be able to continue to accept connections from multiple sources.
Stuart Snaith, Blyth, Northumberland, England
http://www.devx.com
1 01 TECH TIPS
For VB Developers
Next, call the subroutine in the GotFocus event of any text field:
Private Sub txtPubID_GotFocus() SelectWholeText txtPubID End Sub Private Sub txtTitle_GotFocus() SelectWholeText txtTitle End Sub
Hard-coding a scrollbar with a predetermined width or height is not a good idea because these might vary depending on the users display settings (accessibility options, desktop themes, and so on). Call GetSystemMetrics to always ensure the proper value for this metric.
Michael B. Kurtz, McKees Rocks, Pennsylvania
EASY CONFIRMATION
Sometimes you want to give your users the chance to confirm that they wish to proceed with the closure of a form. Instead of using a MsgBox function and a Select Case statement, put this code in the Form_Unload event:
Private Sub Form_Unload(Cancel as Integer) Cancel = (MsgBox("Quit now?", vbOKCancel Or _ vbQuestion, "Confirmation Demo") = vbCancel) End Sub
Behind the Exit button and the Exit menu option, put a simple Unload Me. Whenever users choose to exit, they will be asked to confirm their action.
Rae MacLeman, Peterborough, United Kingdom
http://www.devx.com
1 01 TECH TIPS
For VB Developers
VB5, VB6
Level: Advanced
End Function
For brevity, this example omits the code that would add horizontal scrollbars to the List1 and List2 controls, but it is readily available in the Knowledge Base (Q192184).
Matt Hart, Tulsa, Oklahoma
VB5, VB6
Level: Intermediate
http://www.devx.com
1 01 TECH TIPS
For VB Developers
Next MSFlexGrid1.Col = 0 End If Next Next '== li..Start to End Date End Sub
In this code, the database resides in the same directory as the application itself. If you place it in a different directory, use the appropriate path name.
Ron Schwarz, Mt. Pleasant, Michigan
VB5, VB6
Level: Advanced
cbSize As Long fMask As Long fType As Long fState As Long wID As Long hSubMenu As Long hbmpChecked As Long hbmpUnchecked As Long dwItemData As Long dwTypeData As String cch As Long End Type Public Declare Function CallWindowProc Lib "user32" Alias _ "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal _ hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, _ ByVal lParam As Long) As Long Public Declare Sub CopyMemory Lib "kernel32" Alias _ "RtlMoveMemory" (hpvDest As Any, hpvSource As Any, _ ByVal cbCopy As Long) Public Declare Function GetMenuItemInfo Lib "user32" Alias _ "GetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As _ Long, ByVal b As Boolean, lpMenuItemInfo As _ MENUITEMINFO) As Long Public Declare Function SetWindowLong Lib "user32" Alias _ "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As _ Long, ByVal dwNewLong As Long) As Long Public Const GWL_WNDPROC = (-4) Public Const WM_MENUSELECT = &H11F Public Const MF_SYSMENU = &H2000& Public Const MIIM_TYPE = &H10 Public Const MIIM_DATA = &H20 Public origWndProc As Long Public Function AppWndProc(ByVal hwnd As Long, ByVal Msg _ As Long, ByVal wParam As Long, ByVal lParam As Long) _ As Long Dim iHi As Integer, iLo As Integer Select Case Msg Case WM_MENUSELECT Form1.lblStatus.Caption = "" CopyMemory iLo, wParam, 2 CopyMemory iHi, ByVal VarPtr(wParam) + 2, 2 If (iHi And MF_SYSMENU) = 0 Then Dim m As MENUITEMINFO, aCap As String m.dwTypeData = Space$(64) m.cbSize = Len(m) m.cch = 64 m.fMask = MIIM_DATA Or MIIM_TYPE If GetMenuItemInfo(lParam, CLng(iLo), _ False, m) Then aCap = m.dwTypeData & Chr$(0) aCap = Left$(aCap, _ InStr(aCap, Chr$(0)) - 1) Select Case aCap Case "&Open": _ Form1.lblStatus.Caption = _ "Open a file" Case "&Save": _ Form1.lblStatus.Caption = _ "Save a file" End Select End If End If End Select AppWndProc = CallWindowProc(origWndProc, hwnd, Msg, _ wParam, lParam) End Function
10
http://www.devx.com
1 01 TECH TIPS
For VB Developers
VB5, VB6
Level: Intermediate
Add an AutoSelect property as a Boolean value, so the developer can decide when to use the autoselecting functionality. The Property Let procedure sets the value of the module-level Boolean variable mfAutoSelect. Check the value of mfAutoSelect in the KeyPress event:
Private Sub cbo_KeyPress (KeyAscii as Integer) If mfAutoSelect Then ' Call generic function to select the first matching ' value in the list. Call ComboBox_AutoSelect(cbo, KeyAscii) End if End Sub
You should also make a number of simple but useful improvements to the standard combo box control. First, add a variant ItemData property, create a LimitToList property similar to combo boxes in Access, and select the text in the GotFocus event. If you set the Appearance property to Flat, the combo box still appears in 3-D, so work around this bug. By wrapping this code in an ActiveX control, you reduce the code in your applications.
Craig Randall, Wilmington, Massachusetts
Now, instead of explicitly changing the mouse pointer to an hourglass and back and manually disabling and re-enabling forms, use this code at the beginning of your event procedures:
Dim objHourglass as clsHourglass Set objHourglass = New clsHourglass
With this approach, the Initialize event changes the mouse pointer to an hourglass and disables the current form when the procedure creates the objHourglass. The hourglass cant accidentally be left on because when the objHourglass variable loses scope, the Terminate event fires, which returns the mouse pointer to its original state and re-enables the form if its still loaded. By using the EnableWindow API call, you cant reload a form
Supplement to Visual Basic Programmers Journal FEBRUARY 1999 11
1 01 TECH TIPS
For VB Developers
accidentally if it was unloaded during the time the objHourglass object had life. That could happen if you used the syntax frmCurrent.Enabled = True. Multiple instances of objHourglass wont cause the mouse pointer to flicker, because the mouse pointers current state is checked and saved before setting it to an hourglass. If the user keeps clicking on the form while the hourglass is shown, the extra clicks are discarded because the form isnt enabled. And unlike earlier versions of VB, a form doesnt deactivate when disabled, so the title bar doesnt flicker.
Craig Randall, Wilmington, Massachusetts
clsGeneric and declare the control using WithEvents. You can then trap the events in one place. Create a collection of the clsGeneric class and keep adding controls to the collection. When you end the app, set the Collection object to Nothing. You can use control arrays, but if youre using them across the forms, you dont have to repeat the code:
' --- Save the following code in clsGeneric.cls Option Explicit Public WithEvents txtAny As TextBox Private Sub txtAny_GotFocus() If Len(Trim$(txtAny)) > 0 Then txtAny.SelStart = 0 txtAny.SelLength = Len(txtAny) End If End Sub ' -- Save the following code in clsGenerics.cls Option Explicit Private mColGenerics As New Collection Public Function Add(ByVal txtAny As TextBox, Optional _ ByVal Key As String = "") As clsGeneric Dim clsAny As New clsGeneric Set clsAny.txtAny = txtAny If Key = "" Then mColGenerics.Add clsAny Else mColGenerics.Add clsAny, Key End If Set Add = clsAny ' Return a reference to the new textbox End Function Public Function Count() As Long Count = mColGenerics.Count End Function Public Sub Delete(ByVal Index As Variant) mColGenerics.Remove Index End Sub Public Function Item(ByVal Index As Variant) As clsGeneric Set Item = mColGenerics.Item(Index) End Function Public Function NewEnum() As IUnknown Set NewEnum = mColGenerics.[_NewEnum] End Function ' -- In any form or global module where you want to have ' -- this generic textboxes Private clsTexts As New clsGenerics ' In form load add the controls to the collection like this. clsTexts.Add Text1 clsTexts.Add Text2 clsTexts.Add Text3 ' You can even declare clsTexts globally and keep adding ' controls in whatever forms needed.
To use this function, put a picture in a forms Picture property, and insert this code in that form:
Private Sub Form_MouseUp(Button As Integer, Shift _ As Integer, X As Single, Y As Single) Dim RGB_Point As RGB_Type RGB_Point = ToRGB(Point(X, Y)) Caption = RGB_Point.R & " " & RGB_Point.G & " " & _ RGB_Point.B End Sub
Click on different places on the picture. VB3 users must return the values differently, because VB didnt support the return of a user-defined type until VB4.
Brian Donovan, Bakersfield, California
VB5, VB6
Level: Intermediate
http://www.devx.com
1 01 TECH TIPS
For VB Developers
You can use similar coding to change other properties on different pages, such as repositioning by setting up a Select Case block on SSTab1.Tab for more complex instructions.
Marvin Boehm, Skokie, Illinois
Robert Gelb, Huntington Beach, California Supplement to Visual Basic Programmers Journal FEBRUARY 1999 13
http://www.devx.com
1 01 TECH TIPS
For VB Developers
Option Explicit Private Sub cmdTest_Click() Dim aDynamic() As Integer MsgBox IsArrayEmpty(aDynamic) ReDim aDynamic(8) MsgBox IsArrayEmpty(aDynamic) End Sub
Just as you enclose a string literal with quotes (Hello), you can enclose Date literals with pound signs (#07/07/1998#). So, these are all valid Date literals: #July 7, 1998#, #7-JUL-98#, and #07/07/1998#.
James Bragg, received by e-mail
VB5, VB6
Level: Beginning
You can easily modify this function to make it a property of a Collection class.
Alan Borowski, Cudahy, Wisconsin
VB5, VB6
Level: Intermediate
With this code included in a code module, you need to type only Globals and VB lists them all.
Bennett Sy, Toronto, Ontario, Canada
VB5, VB6
Level: Beginning
You can resize all columns, taking the text in column headers into account by passing True as the second argument:
http://www.devx.com
14
1 01 TECH TIPS
For VB Developers
If you pass False as the second argument, the text in column headers is ignored in determining the correct width.
Marco Losavio, Gioia del Colle, Italy
the FileName property that contains the complete name of this file (path + name). If the user selects multiple files, the returned value contains the directory name, followed by all the names without the path. If you use the cdlOFNExplorer flag, the separator character is the Null character. If youre using VB6, you can use the Split function to quickly parse the returned value:
CommonDialog1.Filter = "All files (*.*)|*.*" CommonDialog1.FilterIndex = 1 CommonDialog1.Flags = cdlOFNAllowMultiselect Or _ cdlOFNFileMustExist Or cdlOFNExplorer CommonDialog1.MaxFileSize = 10240 CommonDialog1.CancelError = True CommonDialog1.Filename = "" CommonDialog1.ShowOpen If Err = 0 Then ' Parse the result into an array of strings Dim names() As String, i As Integer names() = Split(CommonDialog1.Filename, vbNullChar) ' Print file names, including path If UBound(names) = 0 Then ' only one file was selected Print names(0) Else ' multiple files were selected For i = 1 To UBound(names) Print names(0) & "\" & names(i) Next End If End If
This approach lets you save some space in the compiled EXE file and use fewer resources at run time.
Francesco Balena, Bari, Italy
VB6
Level: Beginning
VB5, VB6
Level: Beginning
The returned strings format depends on how many files the user selects. If the user selects only one file, the string returns in
For more information on this topic, look in VBs Help file under SysInfo.
Francesco Balena, Bari, Italy
http://www.devx.com
15
1 01 TECH TIPS
For VB Developers
VB5, VB6
Level: Beginning
Case "TextBox" Print #filenum, ctrl.SelText Case "RichTextBox" Print #filenum, RichTextBox1.SelRTF End Select End Sub
To move and resize the form automatically when the user moves the taskbars, creates new taskbars, or hides them, you have to trap the SysInfo controls SettingChanged event:
Private Sub SysInfo1_SettingChanged(ByVal Item As Integer) Const SPI_SETWORKAREA = 47 If Item = SPI_SETWORKAREA Then MoveForm End If End Sub
If you pass a control or an object that exposes a default property, the routine incorrectly reports the default property type:
TestType Text1 ' displays "It's a string"
For more information on this topic, look in VBs Help file under SysInfo.
Francesco Balena, Bari, Italy
For more information on this topic, look in VBs Help file under VarType.
Francesco Balena, Bari, Italy
VB5, VB6
Level: Intermediate
You can also easily check whether a drive has enough free space on it, using the Drive objects FreeSpace property:
Print "Drive C: has " & dr.FreeSpace " bytes free."
To avoid this problem and gain additional benefits such as the ability to use a Select Case block, use the TypeName function instead:
Sub SaveSelectedText(ctrl As Control, filenum As Integer) Select Case TypeName(ctrl)
For more information, look in VBs Help file under Dictionary and FileSystemObject.
Francesco Balena, Bari, Italy http://www.devx.com
16
1 01 TECH TIPS
For VB Developers
In reality, however, this isnt always the case. For example, if the field youre checking is of value Null, then the test also returns Null, invoking the Else clause. A quick, though perhaps obscure solution, is to check for equality instead:
If ![Name] = "abc" Then Debug.Print "=" Else Debug.Print "<>" End If
Pressing Command1 starts the animation; Command2 stops it. This works for all shape types.
George Hughen, Hamilton, Massachusetts
http://www.devx.com
17
1 01 TECH TIPS
For VB Developers
After trapping these coordinates, pass them to the HitTest method of the ListView control during the DoubleClick event to determine whether a user has double-clicked on a particular ListItem object:
Private Sub ListView1_DblClick() Dim lListItem As ListItem Set lListItem = ListView1.HitTest(sngListViewX, _ sngListViewY) If (lListItem Is Nothing) Then MsgBox "You did not double-click on a ListItem." Else MsgBox "You double-clicked on ListItem=" & _ lListItem.Text End If Set lListItem = Nothing End Sub
This loop, located in the Form_Load event of a form with a number of controls on it, loops through all the controls and prints the name of each windowed control twice, demonstrating that it has correctly located the control without looping through the control collection.
Jeremy Adams, Tiverton, Devon, United Kingdom
18
http://www.devx.com
1 01 TECH TIPS
For VB Developers
' foreground color for the Controls etc. Call Msghook1.InvokeWindowProc(msg, wp, lp) ' Set the background mode to transparent Call SetBkMode(wp, TRANSPARENT) ' Get the stock null brush and return it ' The brush does nothing when the control ' paints using it, hence giving the ' transparency effect result = GetStockObject(NULL_BRUSH) Case Else ' [Replace this line with your own code ' to call the original windowproc] result = Msghook1.InvokeWindowProc(msg, wp, lp) End Select End Sub
VB6
Level: Beginning
This code works for option buttons and checkboxes. I havent found any side effects yet. You can make other controls transparent in a similar way, but some of themincluding textboxes and listboxesdont work correctly because the background doesnt get erased. You can probably get them to work correctly with additional code. Frames are even harder to get to work correctly; I resorted to using a button created using CreateWindowEx with the BS_GROUPBOX style. Even then, I had to return a brush of approximately the color of the background image; otherwise, you could see the line under the text for the frame.
Jeremy Adams, Tiverton, Devon, United Kingdom
http://www.devx.com
19
1 01 TECH TIPS
For VB Developers
hDesktop = OpenDesktop("screen-saver", 0&, 0&, _ MAXIMUM_ALLOWED) If hDesktop = 0 Then If Err.LastDllError = 5 Then NTSaverRunning = True End If Else Templong = CloseDesktop(hDesktop) NTSaverRunning = True End If End Function
Keep in mind this functionGetCaptionFontreturns only the name of the font. However, all the other font information is there in the LOGFONT structures as well.
Ben Baird, Twin Falls, Idaho
VB6
Level: Intermediate
The Split function does the opposite, first splitting a longer string into individual components delimited by the selected separator, then loading the components into a string array. If you couple this feature with a VB6 function to return an array, you can easily build a routine that loads a text file into a string array:
Function StringArrayLoad(Filename As String) As String() Dim f As Integer f = FreeFile Open Filename For Input As #f StringArrayLoad = Split(Input$(LOF(f), f), vbCrLf) Close #f End Function
20
1 01 TECH TIPS
For VB Developers
Agency, but it does protect the data from 99 percent of prying eyes, which is good enough for everything I ever need to do. Its only a few lines of code, but it encrypts as much data as you can fit into a string, and does so quickly:
Private Sub Form_Click() Dim szTest As String szTest = "My dog has fleas." ''' Passing the string through the function once ''' encrypts it. szTest = szEncryptDecrypt(szTest) Debug.Print szTest ''' Passing the string through the function again ''' decrypts it. szTest = szEncryptDecrypt(szTest) Debug.Print szTest End Sub Function szEncryptDecrypt(ByVal szData As String) As String ''' This key value can be changed to alter the ''' encryption, but it must be the same for both ''' encryption and decryption. Const KEY_TEXT As String = "ScratchItYouFool" ''' The KEY_OFFSET is optional, and may be any ''' value 0-64. ''' Likewise, it needs to be the same coming/going. Const KEY_OFFSET As Long = 38 Dim bytKey() As Byte Dim bytData() As Byte Dim lNum As Long Dim szKey As String For lNum = 1 To ((Len(szData) \ Len(KEY_TEXT)) + 1) szKey = szKey & KEY_TEXT Next lNum bytKey = Left$(szKey, Len(szData)) bytData = szData For lNum = LBound(bytData) To UBound(bytData) If lNum Mod 2 Then bytData(lNum) = bytData(lNum) Xor (bytKey(lNum) _ + KEY_OFFSET) Else bytData(lNum) = bytData(lNum) Xor (bytKey(lNum) _ - KEY_OFFSET) End If Next lNum szEncryptDecrypt = bytData End Function
This code uses the Replace function to replace the searched substring with another substring one character shorter. This means the difference between the original string and the string returned by the Replace function is equal to the number of occurrences of the substring.
Francesco Balena, Bari, Italy
VB6
Level: Beginning
http://www.devx.com
21
1 01 TECH TIPS
For VB Developers
CombineRgn hRgn2, hRgn2, hRgn1, RGN_AND 'Return the region handle DeleteObject hRgn1 GetTextRgn = hRgn2 End Function Private Sub Form_DblClick() 'Need to be able to close the form Unload Me End Sub Private Sub Form_Load() Dim hRgn As Long Me.Font.Name = "Wingdings" Me.Font.Size = 200 hRgn = GetTextRgn() SetWindowRgn hwnd, hRgn, 1 End Sub Private Sub Form_MouseDown(Button As Integer, Shift _ As Integer, X As Single, Y As Single) 'Give us some way to move the form ReleaseCapture SendMessage hwnd, WM_NCLBUTTONDOWN, HTCAPTION, ByVal 0& End Sub
In VB, expressions in parentheses are evaluated before theyre processed. So by putting parentheses around the control name, youre telling it to evaluate it. Because a control cant be evaluated, it gives you the value of the default property. This code actually passes the Text string valuebecause Text is the default propertyto the subroutine instead of passing the control. Because the routine expects a textbox and not a string, it generates the type mismatch.
Deborah Kurata, Pleasanton, California
While this is a sort of novelty shape for a form, you can give a form any shape you want, provided you have a way to create the shape of the region. Look at the various region-related API calls to find methods of creating regions other than using font characters.
Ben Baird, Twin Falls, Idaho
VB6
Level: Beginning
VB6
Level: Beginning
You can also call the subroutine without the Call statement. However, if you dont include the Call statement, you cant include parentheses:
doFormat (txtPerson)
22
http://www.devx.com
1 01 TECH TIPS
For VB Developers
To more easily compare two floating numbers, use the new VB6 Round function, which rounds a number to the desired number of decimal digits. For example, you can rewrite the previous test like this:
' the difference is less than 1E-12 MsgBox (Round(d, 12) = 1) ' displays "True"
nisms are optional. The creator of the form can forget to write code to handle the events, or forget to add a public function, or even name it differently from what the class expects. To prevent this, I write an IGridCallback interface definition:
IGridCallback Public Function GetTableName() As String End Function
You can also use this method to check whether A and B variables contain values that match up to their 12th decimal digit:
If Round(A B, 12) = 0 Then Print "Equal"
The SubclassGrid routine displays an error message box if it doesnt find the IGridCallback interface in the form using it. This mechanism guarantees that everyone using the SubclassGrid class must implement the IGridCallback interface. Because implementing the interface means you must support each method in the interface, it also means everyone using the class has the correct functions.
Jose Mojica, Davie, Florida
In a production app, use FreeFile rather than hard-code the file handles.
Russell Davis, Garden Grove, California
VB5, VB6
Level: Advanced
Set your variablein this illustration, CurrentNodeto the node passed in the event:
Private Sub tvwDB_NodeClick(ByVal node As node) Set CurrentNode = node
You can now access CurrentNodes properties from anywhere on the form:
Debug.Print CurrentNode.Key Debug.Print CurrentNode.Text
In some instances, the class needs to get information from the form using the class. To get information from the form, the class can fire an event, or the form can have a Public property, function, or method that the class can call. However, both mechahttp://www.devx.com
23
1 01 TECH TIPS
For VB Developers
into four groups of four characters each, using a complex string expression:
' X holds the sequence of 16 digits CreditCardNum = Left$(x, 4) & " " & Mid$(x, 5, 4) & " " & _ Mid$(x, 9, 4) & " " & Right$(x, 4)
The Format function lets you accomplish the same result in a more readable and efficient way:
CreditCardNum = Format$(x, "!@@@@ @@@@ @@@@ @@@@")
VB6
Level: Intermediate
VB5, VB6
Level: Intermediate
http://www.devx.com
1 01 TECH TIPS
For VB Developers
Using TypeOf, you can query at run time which interfaces these objects support: Query TypeOf objMyFTE Is CFullTimeEmployee TypeOf objMyFTE Is IEmployee TypeOf objMyFTE Is IEmp2 TypeOf objMyFTE Is CPartTimeEmployee TypeOf objMyFTE Is Object TypeOf objMyFTE Is IUnknown TypeOf objMyPTE Is CPartTimeEmployee TypeOf objMyPTE Is IEmployee TypeOf objMyPTE Is IEmp2 TypeOf objMyPTE Is Object TypeOf objMyPTE Is IUnknown TypeOf objMyPTE Is CFullTimeEmployee Return True True True False True True True True False True True False
VB5
Level: Intermediate
Example 2:
Text1.SelStart = Len(Text1.text) Text1.SelText = MyString
In the first example, you must copy the complete text from the textbox into a separate buffer to perform concatenation with the string MyString. You then need to copy the resulting string back to the textbox. This code requires allocating additional memory and performing two copy operations. The code in the second example does not need an additional buffer. The concatenation of strings is executed inside the textbox buffer without transferring the text first outside the textbox and then back inside. This code is much faster and less memory-extensive. This second approach has an additional advantage of setting an insertion point directly at the end of the displayed text. In the first example, the insertion point is initially set to the beginning of the text, then transferred to the end, causing the control to flicker on the screen.
Krystyna Zyzdryn, Palm Beach Gardens, Florida
Rajesh R. Vakharia, Mumbai, India http://www.devx.com Supplement to Visual Basic Programmers Journal FEBRUARY 1999 25
1 01 TECH TIPS
For VB Developers
wasnt in version 1 does it, as well as removing a class that wasnt in version 1. When working with ActiveX DLLs, the only way to ensure ongoing compatibility is by updating your reference build after each change in the interface(s) it exposes. You should always point the reference to your last-shipped build, which assures that each version is compatible with the last-shipped version.
Thomas Weiss, Deerfield, Illinois
Heres an example:
Private Sub cmdQuit_Click() ' Move Cursor to command button "cmdAreYouSure" SetMouseFocus cmdAreYouSure End Sub
The object does terminate, but a new instance is immediately instantiated, so it appears the object hasnt been set to Nothing. You can test this by going to the Immediate window and testing ?(oSomeObject = Nothing). It displays False. If you declare an object and explicitly set it to New, you can set the object to Nothing. For example, if you declare an object using this code, the object will be set to Nothing:
Dim oSomeObject Set oSomeObject '.... some code Set oSomeObject As SomeClass = New SomeClass ... = Nothing
ASSURE COMPATIBILITY
When working with ActiveX DLLs, many VB programmers build a reference DLL first, copy it to a subdirectory of the project directory, and set version compatibility to binary with a pointer to the reference build. Many of these same programmers believeor have been taughtthey can now forget about compatibility because VB prevents them from ever building an incompatible version. Theyre dead wrong. Suppose you later add a method to one of the public classes in the project, which is defined as:
Public Sub DoSomething(Prm1 As String, Prm2 as Integer)
Version 2, which includes the new DoSomething method, is built without VB complaining, because the new build is version-compatible. For example, you might modify and recompile client applications to take advantage of the new method. Suppose you change the Prm2 parameter of DoSomething from Integer to Long to prevent an overflow error. Ordinarily, such a change breaks backward compatibility. But, because the reference build is version 1, which doesnt have the DoSomething method, VB assumes its completely new and happily builds version 3. The truth, however, is that this change does break compatibility, and all the client applications of version 2 crash with runtime error 430, Class doesnt support Automation. Changing a parameters type isnt the only way a programmer can fool VB into thinking its maintaining binary compatibility. In fact, removing or changing any method or property that
26 FEBRUARY 1999 Supplement to Visual Basic Programmers Journal
http://www.devx.com
1 01 TECH TIPS
For VB Developers
PositionOfDeclaration = InStr(PositionOfDeclaration _ + 1, FileContents, "Private Const ") If PositionOfDeclaration > 0 Then 'we've found a constant: StartOfConstantName = PositionOfDeclaration _ + Len("Private Const ") EndOfConstantName = InStr( _ StartOfConstantName, FileContents, " ") ConstantName = Mid$(FileContents, _ StartOfConstantName, EndOfConstantName _ - StartOfConstantName) 'if the constant is not 'referenced beyond its 'declaration, then it's dead: If InStr(EndOfConstantName, FileContents, _ ConstantName) = 0 Then lstDeadConstants.AddItem ConstantName End If End If Loop Until PositionOfDeclaration = 0 End Sub
Exit Sub End If ' If the difference is > 1, the ' pointer is already set to where it ' should be. Leave without setting ' pointer. If (Abs(nHourglass - nDefault) > 1) Then Exit Sub ' If one is ahead, set the ' mousepointer to it If (nHourglass > nDefault) Then Screen.MousePointer = vbHourglass Else Screen.MousePointer = vbDefault End If End Sub
VB5, VB6
Level: Beginning
http://www.devx.com
27
1 01 TECH TIPS
For VB Developers
VB6
Level: Intermediate
1. ADVANCED VISUAL BASIC http://vb.duke.net This tightly focused Visual Basic site features a collection of articles, a discussion board, and an interface to WinError, a service for interpreting Windows error numbers. 2. JOE GARRICKS WORLD OF VISUAL BASIC http://www.citilink.com/~jgarrick/vbasic This site is loaded with useful information, reusable code, tips and tricks, a Q&A section, and a search engine. The site specializes in database applications and offers several articles on database programming and security. 3. VISUAL BASIC ONLINE
http://www.vbonline.com This e-zine features tips and tricks, product reviews, and more. You can purchase component software from the online catalog.
Double-click on the file name to update your system registry. Next time you start VB6, its code windows will maximize automatically.
Phil Weber, Tigard, Oregon
Visual Studio 6
Level: Beginning
4. VISUALBASICSOURCE
http://www.kather.net/VisualBasicSource This site features a large quantity of tips and code snippets, although the organization and architecture of the site could use some improvement.
5. CARL AND GARYS HOME PAGE http://www.cgvb.com Carl and Garys Home Page, the first VB Web site, has been updated to include a categorized list of links, as well as new sections on ASP development, JavaScript, Microsoft IIS, and more. 6. ACTIVE-X.COM
http://www.active-x.com Active-X.com offers an extensive array of commercially developed components and links to their manufacturers Web sites.
Visual Studio 6
Level: Beginning
7. ONE-STOP SOURCESHOP http://www.mvps.org/vb This site features various techniques for manipulating the Windows API from VB, including a really useful one for deciphering error codes. 8. VBNET http://www.mvps.org/vbnet This easy-to-use site contains an assortment of about 70 VB code tips, a FAQ listing, articles, VB links, and a few files to download. 9. COOL VISUAL BASIC
http://www.beadsandbaubles.com/coolvb/index3.shtml This sites value lies in its message boards and help desk. Youll also find product reviews and a list of links to VB-oriented companies and developers.
10. VB HELPER http://www.vb-helper.com VB Helper is a useful site for beginning and intermediate VB programmers to browse and learn. It features several how-to articles and a number of example code files available for download.
28
http://www.devx.com