11.07.2015 Views

101 Tech Tips - Visual Studio Magazine - One-Stop Source Shop

101 Tech Tips - Visual Studio Magazine - One-Stop Source Shop

101 Tech Tips - Visual Studio Magazine - One-Stop Source Shop

SHOW MORE
SHOW LESS
  • No tags were found...

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

For even more tricks and tips go towww.vbpj.com or www.vcdj.comWelcome to the First Edition of theVSM <strong>Tech</strong>nical <strong>Tips</strong> Supplement!The editors of <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> are pleased tobring you these invaluable tips, techniques, andworkarounds, submitted and reviewed by professionaldevelopers. Instead of typing the code publishedhere, download the tips for free from the VSMWeb site at www.vbpj.com or www.vcdj.com.We know you’ve uncovered your own tips andtricks—send them to us at vsmtips@fawcette.com.Include a clear explanation of what the techniquedoes, why it’s useful, and what language(s) andversion(s) it applies to. Please limit code length to20 lines. Don’t forget to include your mailing address,and let us know your compensation preferenceper published tip: $25, a new one-year VSMsubscription, or a one-year extension to your existingVSM subscription.VB.NETLevel: IntermediateReturn Strings From an APIIn .NET, strings are immutable: When you pass them out to an API,you can’t modify them. However, VB.NET applies the VBByRefStrunmanaged type-marshaling attribute to the string. This allowsVB.NET to create a temporary buffer, copy that back to a newstring, then point the original string to the new string:Public Declare Function GetWindowText _Lib "User32.Dll" _(ByVal hwnd As Int32, _ByVal lpString As String, _ByVal cch As Int32) As Int32To use this declaration, simply initialize the string to the right size:Dim s As String = Space(256)Dim rtn as Int32 = GetWindowText(hwnd, s, 256)The API declaration is equivalent to:Public Declare Function GetWindowText _Lib "User32.Dll" (ByVal hwnd As Int32, _ _ByRef lpString As String, _ByVal cch As Int32) As Int32VB.NET, however, doesn’t allow you to specify that marshalingattribute on parameters, so you must use the first declaration. Usea StringBuilder object as an alternative to using the VBByRefStrattribute.VB4, VB5, VB6Level: BeginningCount the Number of Elements in an ArrayThis function computes the number of elements of any onedimensionalarray—it sure beats ripping open the SAFEARRAYarray descriptor. Use it when you’re not sure whether an array isone- or zero-based:Public Function CountElements( _ByVal SimpleArray As Variant) As Long' Ignore error if array not dimensionedOn Error Resume NextIf Not IsArray(SimpleArray) Then Exit FunctionCountElements = Abs((LBound(SimpleArray)) - _(UBound(SimpleArray))) + 1End FunctionVB4/32, VB5, VB6Level: Beginning—Monte Hansen, Ripon, Calif.Extend Registry FunctionalityAn (undocumented) feature of VB’s native *Setting Registry functionsis that they can create and access multilayer hierarchies suchas this:VB and VBA Project SettingsApplicationPluginSectionSubsectionKey = "Value"You can do this easily—simply add a “\” character between theparent entry and its child entry. Then you can use the Registry asyou’d use a folder with subfolders. Check out these code examplesthat create and read structures such as the preceding one:Call SaveSetting("Application\Plugin", _"Section\Subsection", "Key", "Value").Print SaveSetting("Application\Plugin\Section", _" Subsection", "Key", "Value")C#Level: Beginning—Chris Hynes, Fort Washington, Md.Use String Literals to Simplify PathsWhen you need to set a string to a local or network path, use astring literal to avoid writing repeating backslashes. For instance,this code:string sLocalPath = "C:\\directory\\file.txt";string sNetworkPath ="\\\\machinename\\directory\\file.txt";Becomes this:string sLocalPath = @"C:\directory\file.txt";string sNetworkPath =@"\\machinename\directory\file.txt";—Robert Lair, Springboro, Ohio—Bill McCarthy, Barongarook, Victoria, AustraliaSEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 1


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVB5, VB6Level: BeginningLet VB Spell the Control Name for YouI love the way VB’s IntelliSense drops down a list of control names,properties, and methods as soon as I type the period after theobject’s name. But when you’re coding in a form’s own module andreferencing the control name directly, without an object qualifier,you can still get that cool dropdown list. First type “Me.” (with theperiod) and pick the name. Feel free to delete the “Me.” qualifierlater. Here’s an example:Call clsDatabase.LoadMyListBox(Me.MylistBox)VB4/32, VB5, VB6, VBS, SQL 7.0 and upLevel: Intermediate—Frank Ramage, Laurel, Md.Preprocess Nulls in Your RecordsetI’ve seen several examples of VB code dealing with nulls inrecordsets before the value is assigned to a VB control. I often usethe Transact-SQL IsNull statement when working with SQL Server(version 7.0 and later) queries. SQL Server uses IsNull to deal withthe null value if it arises so I don’t have to write additional code tohandle nulls when I process a recordset. For example, in this codethat reads CustomerID and EmailAddress from a Customers Table,SQL Server returns the value “na” if the EmailAddress field is null:set rsCustomerDetails = cn.Execute("Select " & _"CustomerID, IsNull(EmailAddress,'na') as " & _"EmailAddress from Customers")You can’t update the recordset because IsNull appears in theSelect statement. But you’ll find many circumstances where thisdoesn’t matter, such as when using VBScript to build a table in aWeb page.—Robert Bryan, Downer, Australian Capital Territory,AustraliaVB5, VB6Level: IntermediateAdd a Picture Preview Property PageDefining a public property in a user control as Picture (or StdPicture)provides the standard ellipsis next to the property name in VB’sproperty viewer automatically. This pops up a standard dialog toload an image control.Let’s say you have this code in a user control:Public Property Get Picture() As PictureSet Picture = UserControl.PictureEnd PropertyPublic Property Set Picture( _ByVal newPicture As Picture)Set UserControl.Picture = newPicturePropertyChanged "Picture"End PropertyYou can add a standard VB property page to the control thatprovides the standard preview window so you can see what you’reloading. To do this, you open the UserControl in design mode andselect the PropertyPages property. You’ll see a dialog with threeor four choices: StandardPicture, StandardFont, StandardColor,and (for VB6) StandardDataFormat. Simply check the ones youwish to have a custom property added to the UserControl’s otherproperties. Note: Just because you add the property pages doesn’tmean you can access them immediately. You need to assign thepage to specific properties using the Procedure Attributes dialog.VB6Level: IntermediateSplit Strings Cleanly, ReduxIn the 10 th Edition of the “<strong>101</strong> <strong>Tech</strong> <strong>Tips</strong> for VB Developers”supplement [<strong>Visual</strong> Basic Programmer’s Journal February 2000],the “Split Strings Cleanly” function splits an array containing morethan one delimiter in a row efficiently. This functions works greatfor one delimiter, but what if you want to split an array on morethan one delimiter? Adding a few lines of code and using recursioncan enhance the function to handle multiple delimiters.When more than one delimiter is passed into the function, yourejoin the filtered array using the next delimiter, drop the currentdelimiter from the delimiter list, and call the function again:Public Function CleanSplit2( _ByVal Expression As String, _Optional ByVal Delimiters As String = " ", _Optional ByVal Limit As Long = -1, _Optional Compare As VbCompareMethod = _vbBinaryCompare) As VariantDim Substrings() As StringDim <strong>One</strong>Delimiter As StringDim I As Long<strong>One</strong>Delimiter = Mid$(Delimiters, 1, 1)Substrings = Split(Expression, <strong>One</strong>Delimiter, _Limit, Compare)For I = LBound(Substrings) To UBound(Substrings)If Len(Substrings(I)) = 0 ThenSubstrings(I) = <strong>One</strong>DelimiterEnd IfNext IIf Len(Delimiters) = 1 ThenCleanSplit2 = Filter( _Substrings, <strong>One</strong>Delimiter, False)ElseCleanSplit2 = _CleanSplit2(Join( _Filter(Substrings, <strong>One</strong>Delimiter, False), _Mid$(Delimiters, 2, 1)), _Mid$(Delimiters, 2), Limit, Compare)End IfEnd FunctionVB3, VB4, VB5, VB6Level: Intermediate—Stephen Sayabalian, Waltham, Mass.Keep Your Projects IntactVB has always gone out of its way to take care of mundanehousekeeping tasks without bothering you with the details. Butsometimes the best intentions can create unintended problems.When you work with a project, VB automatically keeps track of theproject’s files by maintaining their entries in the VBP project file(MAK in VB3). When you move files around or bring in files fromother projects, VB edits the path information for those files,sometimes creating an incomprehensible mess of upward-movingrelative paths littered with “\..\” steps. The result can be catastrophic—youcan inadvertently edit a different project’s sourcecode, or you can end up missing files when you move a project toanother directory or computer. To avoid these problems, firstmake sure all your working files are in the directories you intended,then edit the VBP file manually in Notepad to remove anyvisually ambiguous path descriptions. Make sure you exit the VBIDE before editing the VBP file.—Ron Schwarz, Hart, Mich.—John Cullen, Pedroucos, Portugal2 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB3, VB4, VB5, VB6Level: BeginningA New Look at the Select CaseWho says the Select Case can evaluate only one statement? TrySelect Case True instead of the If…ElseIf…End If blocks. SelectCase’s more eloquent style makes code easier to read, write, andmaintain. Consider both these routines that reset the controls ona form:' If...ElseIf...End If VersionPublic Sub IfResetForm(frmForm As Form)Dim c As ControlFor Each c In frmForm.ControlsIf TypeOf c Is TextBox Thenc.Text = ""ElseIf TypeOf c Is ComboBox Thenc.ListIndex = -1If c.Style vbComboDropdownList Thenc.Text = ""End IfElseIf TypeOf c Is ListBox Thenc.ListIndex = -1ElseIf TypeOf c Is CheckBox Thenc.Value = vbUncheckedElseIf TypeOf c Is OptionButton Thenc.Value = FalseEnd IfNext cEnd Sub' Select Case True VersionPublic Sub SelResetForm(frmForm As Form)Dim c As ControlFor Each c In frmForm.ControlsSelect Case TrueCase TypeOf c Is TextBoxc.Text = ""Case TypeOf c Is ComboBoxc.ListIndex = -1If c.Style vbComboDropdownList Thenc.Text = ""End IfCase TypeOf c Is ListBoxc.ListIndex = -1Case TypeOf c Is CheckBoxc.Value = vbUncheckedCase TypeOf c Is OptionButtonc.Value = FalseEnd SelectNext cEnd SubThe Select Case True routine is easy to read and debug, whereasthe If…ElseIf…End If routine gives the impression of nesting andmight be more difficult to evaluate at a glance. The Select CaseTrue executes the code block of the first true statement it encounters;however, code tends to flow better when you construct a listof Case statements rather than a series of ElseIf blocks.—Michael C. Stahr, Oxford, OhioVB4/32, VB5, VB6, SQL Server 6.5 and up, Oracle 8i and upLevel: IntermediateChange Oracle and SQL Server PasswordsYou can change database passwords from within VB to controlmore of your application’s security and limit your dependence onan external DBA. This function updates a database password foreither Oracle or SQL Server:Function UpdateLogin(pbOracle As Boolean, _padoConn as ADODB.Connection, _pstrUserId As String, _pstrCurPassword As String, _pstrNewPassword As String) As BooleanDim strSQL As StringOn Error GoTo ErrHandlerUpdateLogin = TrueIf (pbOracle) ThenstrSQL = "ALTER USER " & pstrUserId & _" IDENTIFIED BY " & pstrNewPasswordElsestrSQL = "sp_password '" & _pstrCurPassword & "', '" & _pstrNewPassword & "'"End IfpadoConn.Execute strSQLExit FunctionErrHandler:UpdateLogin = FalseExit FunctionEnd FunctionTo use this, you should connect to the database using the accountyou’re changing.VB4/32, VB5, VB6, VBA, VBSLevel: Intermediate—Andy Clark, Richmond, Va.Generate OLE DB Connection StringsMany VB projects need a database connection string. But there’sno easy way to generate an OLE DB connection string withoutadding a DataEnvironment to your project, setting the values onthe Data Link Properties dialog by selecting Properties from theConnection1 object context menu, and finally retrieving the valuein the Connection<strong>Source</strong> property as your connection string.For a better way, simply paste these nine lines of code intoa text file with a VBS (VBScript) extension, and double-click onthe file:Dim oDataLinks, sRetValSet oDataLinks = CreateObject("DataLinks")On Error Resume Next ' Trap Cancel buttonsRetVal = oDataLinks.PromptNewOn Error Goto 0If Not IsEmpty(sRetVal) Then ' Didn't click CancelInputBox "Your Connection String is listed below.", _"OLEDB Connection String", sRetValEnd IfSet oDataLinks = NothingFollow the usual prompts to place the resulting connection stringin an input box for easy cut-and-pasting. Now any time you need aconnection string for an OLE DB data source, it’s only a doubleclickaway.Note: If you’re using VB, you can add a reference to the MicrosoftOLE DB Service Component 1.0 Type Library (OLEDB32.dll) and usethe Object Browser to explore the additional interfaces the DataLinksobject exposes.VB6Level: Advanced—Anthony T. Petro, Centennial, Colo.Continue After Hitting an ErrorIf you use VB6 to write COM programs that raise errors, it seemsimpossible to continue after hitting one of them. However, the(almost undocumented) commands ALT+F8 and ALT+F5 let youstep and run past an error, respectively, into the error-handlingcode or—more importantly—into the code that called the procedurewhere the error occurred (such as a C++ client). This can beSEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 3


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> Developersa real lifesaver if you’re coding in both C++ and VB and theprograms have to play nicely. You can directly receive errorresults raised in the VB code instead of hacking around it by settingbreakpoints in the client, setting the next line of execution tosomething that returns to the caller, and using the debugger tosave the error code into a variable on the client. Why the “run,”“step over,” and similar buttons don’t do what ALT+F8 and ALT+F5do is beyond me.VB5, VB6Level: Beginning—Michael Nyman, Tualatin, Ore.Generate Rule-Based Random StringsUse this function to generate random strings that abide by certaincriteria. It’s perfect for password generators or strings used in achallenge/response authentication scheme:Public Enum RandomStringOptionsrsoAllChars = 0rsoAllCharsExtended = 1rsoKeyboardChars = 2rsoAlphaNumericChars = 3End EnumPublic Function RandomString(Optional ByVal _MinLength As Long = 20, _Optional ByVal MaxLength As Long = 29, _Optional ByVal ExclusionCharacters As String = _" ", Optional ByVal RandomOption As _RandomStringOptions = rsoAlphaNumericChars) _As String'===================================================' Generates a random string using ...' Max/MinLength: Determines the minimum and' maximum size of the string.' ExclusionCharacters: Characters that cannot' appear in the random string.' RandomOption: Special options used to define' additional rules.'===================================================' Where random string is builtDim Buffer() As Byte' Next character to testDim NextChar As Byte' The lower range of the char tableDim iCharLo As Integer' The upper range of the char tableDim iCharHi As IntegerDim iAs Long' Sanity checkIf MinLength < 1 Or MaxLength < MinLength ThenErr.Raise 5, App.ProductNameEnd IfIf RandomOption = rsoKeyboardChars Then' -- only keyboard characters are supported' Characters 32 through 126 are keyboard' charactersiCharLo = 32: iCharHi = 126ElseIf RandomOption = rsoAlphaNumericChars Then' This range included entire alphanumeric' charactersiCharLo = 48: iCharHi = 122ElseIf RandomOption = rsoAllCharsExtended Then' -- we can use the entire "standard" ascii' character setiCharLo = 0: iCharHi = 127Else ' RandomOption = rsoAllChars' -- we can use the entire character set,' including extended charactersiCharLo = 0: iCharHi = 255End If' Fire up the random number generatorRandomize Timer' Size the buffer to fit a random number size' within the desired string length range.ReDim Buffer(1 To Int((MaxLength - MinLength _+ 1) * Rnd + MinLength))' Loop through the output bufferFor i = LBound(Buffer) To UBound(Buffer)' Loop until "good" character is selectedDo' Get a random character in the character' set rangeNextChar = Int((iCharHi - iCharLo + 1) * _Rnd + iCharLo)' Make sure not in exclusion listIf InStr(ExclusionCharacters, _Chr(NextChar)) = 0 Then' Check if AlphaNumeric?If RandomOption = rsoAlphaNumericChars _ThenSelect Case NextCharCase 48 To 57, 65 To 90, 97 To 122' within the alphanumeric range' of charactersExit DoCase Else' just keep on looping until' alphanumeric' character generated.End SelectElse' we have a non-excluded charExit DoEnd IfEnd IfLoop' Assign this char, and get nextBuffer(i) = NextCharNext i' Return the resulting stringRandomString = StrConv(Buffer, vbUnicode)End FunctionC#Level: Beginning—Monte Hansen, Ripon, Calif.Close a Windows FormA Close button, which closes a form when the user clicks on it, isone of the most common interface controls added to a Windowsform. Unfortunately, the wizard does not generate the code foryou, so you must do it manually. Add a button to the form; set itstext to Close, Cancel, or Exit; and give it a meaningful name suchas m_CloseButton. Next, create a Click event method handler(such as OnCloseButtonClick) and add a new delegate, initializedwith that handler to the Close button’s Click event:public class MyForm : Form{protected Button m_CloseButton;public MyForm(){InitializeComponent();CancelButton = m_CloseButton;}private void InitializeComponent(){m_CloseButton = new Button();4 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comm_CloseButton.Click +=new EventHandler(OnCloseButtonClick);Controls.Add(m_CloseButton);}protected void OnCloseButtonClick(object sender,EventArgs e){Close(); //calls Dispose() for you as well}}You can also double-click on the Close button in the visualdesigner and let <strong>Visual</strong> <strong>Studio</strong>.NET generate this code for you.In your Close button Click event handler, simply call your baseclass (System.Windows.Forms.Form) Close( ) method to closethe form. In addition to closing the form, the Form.Close( )implementation also calls Dispose( ) for you, so you don’t need tocall it explicitly yourself.Finally, you need to handle the event of the user hitting theEscape key. The convention in Windows is that this action shouldclose the form, just as if the user clicked on the Close button. Inyour form constructor, after the call to InitializeComponent( ), setyour base class CancelButton property to the Close button youjust added. This will redirect the Escape event to your button, asif the user clicked on it.—Juval Lowy, San Jose, Calif.author of COM+ Services - Mastering COM and .NETComponent Services [O’Reilly, 2001]VB6, SQL Server 6.5 and upLevel: IntermediateLet MTS Handle Transaction ManagementWhen you call a stored procedure in SQL Server that performs datamanipulation on the database from a Microsoft Transaction Server(MTS) transaction, let MTS handle all the transaction management.Don’t put a BEGIN TRANSACTION | COMMIT TRANSACTION |ROLLBACK TRANSACTION in the stored procedure. The transactionyou create in the stored procedure doesn’t enlist in the MTStransaction, so MTS isn’t notified when you handle the SQL errorsmanually. This means an error in your stored procedure won’tforce the rollback of the MTS transaction’s other parts. The MTStransaction returns a success notification even when part of thetransaction failed.VB4/32, VB5, VB6Level: Beginning—Jason Rein, Thompson’s Station, Tenn.Return File Version InfoRegarding the “Retrieve File Version Information” tip in the 11 thEdition of the “<strong>101</strong> <strong>Tech</strong> <strong>Tips</strong> for VB Developers” supplement[<strong>Visual</strong> Basic Programmer’s Journal March 2001], I have a shorterfunction that achieves the same task. To use the FileSystemObject,you need to reference the Microsoft Scripting Runtime:Public Function GetExecutableFileVersion(ByVal _Filename As String) As StringDim FileObj As Scripting.FileSystemObject' Create ObjectSet FileObj = New Scripting.FileSystemObjectIf FileObj.FileExists(Filename) ThenGetExecutableFileVersion = _FileObj.GetFileVersion(Filename)End If' Free ObjectSet FileObj = NothingEnd Function—Simon Murrell, Bedfordview, Gauteng, South AfricaVB4/32, VB5, VB6, VBSLevel: Intermediate✰✰✰✰✰ Five Star TipCreate ISAM Files Out of Thin Air<strong>Visual</strong> Basic Programmer’s Journal once published a tip on how touse undocumented Jet/SQL features to create a new ISAM file inthe format of your choice—such as Excel, dBase, Paradox, HTML,and Lotus—without having to use automation with the objectmodel or even having the destination file type’s application on theuser’s machine [“Export Data to ISAM Databases,” 6 th Edition of the“<strong>101</strong> <strong>Tech</strong> <strong>Tips</strong> for VB Developers” supplement, <strong>Visual</strong> BasicProgrammer’s Journal February 1998].Microsoft still says that method doesn’t exist. Here’s anotherit says doesn’t exist, but it does as of ADO/ADOX 2.1. Start a newVB project and add a reference to ADO and ADOX (Microsoft ADOExtensions for DDL and Security):Dim cn As ConnectionDim cat As CatalogDim tbl As TableDim fld As ColumnSet cn = New ConnectionWith cn.ConnectionString = _"Provider=Microsoft.Jet.OLEDB.4.0;" & _"Extended Properties=Excel 8.0;Data <strong>Source</strong>=" _& App.Path & "\anewfile.xls".OpenEnd WithSet cat = New CatalogWith cat.ActiveConnection = cnSet tbl = New Tabletbl.Name = "ANewSheet"Set fld = New Columnfld.Name = "MyCol1"fld.Type = adWCharfld.DefinedSize = 30tbl.Columns.Append fld.Tables.Append tblSet tbl = New Tabletbl.Name = "Another"Set fld = New Columnfld.Name = "Wubba"fld.Type = adWCharfld.DefinedSize = 10tbl.Columns.Append fld.Tables.Append tblcat.Tables.RefreshEnd WithSet fld = NothingSet tbl = NothingSet cat = Nothingcn.CloseSet cn = NothingRun it to see your Excel file created with a Worksheet namedNewSheet and a column named/typed as you specified.Use this to wow your users with multisheet reports. Once youcreate the sheets, you can use ADO to connect to the files andmanipulate them as you would any other ADO data source. But thecoolness of this technique lies not in the fact that you can manipulatean ISAM data source once you have one on your machine—that has always been easy. The coolness is the ability to create thefiles out of thin air in the first place using only ADO.Like the original SQL method, this approach creates mostother ISAMs too. Simply replace the Extended Properties= valuewith the specifier you desire, such as Extended Properties=dBaseIV;, Extended Properties=Paradox 4.x;, and so on.—Robert Smith, Kirkland, Wash.SEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 5


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVB3, VB4, VB5, VB6Level: BeginningTweak the Key and Mouse EventsMany VB objects have KeyDown, KeyUp, MouseDown, MouseUp,and MouseMove events. They’re unique in one respect: They havebuilt-in limitations on what they’re able to do. You often need tocoordinate these events with things outside their immediatescope to use them effectively. For example, you might need yourapp to perform a repetitive action while a mouse button is down.The MouseDown event fires only once—when the button isclicked—so you can’t determine the mouse state outside theMouseDown event easily. Handle these situations by using avariable with sufficient scope to address the required code. Forexample, if you’re coding all the mouse-oriented functionalitywithin the form that contains the control in question, declare aform-level Boolean variable and use it to track the mouse’s state.Set the variable to True when the MouseDown event fires, and toFalse when the MouseUp event fires. When your code executes,have it check the mouse state by testing the variable’s currentvalue. The Mouse events then maintain the variable’s state automatically,and you can track the variables’, hence the mouse, statefrom outside the mouse events.VB5, VB6Level: Advanced—Ron Schwarz, Hart, Mich.Get Dynamic Array InformationUse the GetSafeArrayInfo function to rip the lid off a SAFEARRAY.It allows the caller to identify the number of dimensions andnumber of elements for each dimension (among other things).Element information for each dimension is stored in a one-basedsubarray of SAFEARRAYBOUND structures (rgsabound):Public Type SAFEARRAYBOUND' # of elements in the array dimensioncElements As Long' lower bounds of the array dimensionlLbound As LongEnd TypePublic Type SAFEARRAY' Count of dimensions in this array.cDims As Integer' Flags used by the SafeArray' routines documented below.fFeatures As Integer' Size of an element of the array.' Does not include size of' pointed-to data.cbElements As Long' Number of times the array has been' locked without corresponding unlock.cLocks As Long' Pointer to the data.' Should be sized to cDims:pvData As Long' <strong>One</strong> bound for each dimension.rgsabound() As SAFEARRAYBOUNDEnd TypePrivate Declare Sub CopyMemory Lib "kernel32" Alias _"RtlMoveMemory" (ByVal lpDest As Long, ByVal _lp<strong>Source</strong> As Long, ByVal nBytes As Long)' Pointer to the variants data itemDim lpData As Long' the VARTYPE member of the VARIANT structureDim VType As IntegerConst VT_BYREF As Long = &H4000&' Exit if no array suppliedIf Not IsArray(TheArray) Then Exit FunctionWith ArrayInfo' Get the VARTYPE value from the first 2 bytes' of the VARIANT structureCopyMemory ByVal VarPtr(VType), ByVal _VarPtr(TheArray), 2' Get the pointer to the array descriptor' (SAFEARRAY structure)' NOTE: A Variant's descriptor, padding &' union take up 8 bytes.CopyMemory ByVal VarPtr(lpData), ByVal _(VarPtr(TheArray) + 8), 4' Test if lpData is a pointer or a pointer to' a pointer.If (VType And VT_BYREF) 0 Then' Get real pointer to the array descriptor' (SAFEARRAY structure)CopyMemory ByVal VarPtr(lpData), ByVal _lpData, 4' This will be zero if array not' dimensioned yetIf lpData = 0 Then Exit FunctionEnd If' Fill the SAFEARRAY structure with the array' info' NOTE: The fixed part of the SAFEARRAY' structure is 16 bytes.CopyMemory ByVal VarPtr(ArrayInfo.cDims), _ByVal lpData, 16' Ensure the array has been dimensioned before' getting SAFEARRAYBOUND informationIf ArrayInfo.cDims > 0 Then' Size the array to fit the # of boundsReDim .rgsabound(1 To .cDims)' Fill the SAFEARRAYBOUND structure with' the array infoCopyMemory ByVal VarPtr(.rgsabound(1)), _ByVal lpData + 16, _ArrayInfo.cDims * Len(.rgsabound(1))' So caller knows there is information' available for the array in output' SAFEARRAYGetSafeArrayInfo = TrueEnd IfEnd WithEnd Function—Monte Hansen, Ripon, Calif.Public Function GetSafeArrayInfo(TheArray As _Variant, ArrayInfo As SAFEARRAY) As Boolean'===================================================' Fills a SAFEARRAY structure for the array.' TheArray: The array to get information on.' ArrayInfo: The output SAFEARRAY structure.' RETURNS: True if the array is instantiated.'===================================================6 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB4/32, VB5, VB6, SQL Server 6.5 and up, Oracle 8i and upLevel: IntermediateCompare Oracle and SQL Server StringsOracle and SQL Server treat strings slightly differently—they don’ttrim blank and null characters identically. So you’ll have problemscomparing string data between Oracle and SQL Server. The solution—trimboth blanks and nulls from all strings:Private Function MatchStrings(adoFldOra As _ADODB.Field, adoFldSQLServ As _ADODB.Field) As BooleanDim strOracle As StringDim strSQLServ As StringstrOracle = TrimNulls(adoFldOra.Value)strSQLServ = TrimNulls(adoFldSQLServ.Value)MatchStrings = (strOracle = strSQLServ)End FunctionPrivate Function TrimNulls(pstrIn As String) As _StringDim ndx As IntegerDim pos As IntegerDim strWork As StringstrWork = ""pos = 0ndx = 1Do While ((pos = 0) And (ndx 0))If (Asc(Mid(pstrIn, ndx, 1) 0) And _(Mid(pstrIn, ndx, 1) " ")) Thenpos = ndxEnd Ifndx = ndx - 1LoopIf (pos = 0) ThenTrimNulls = ""Exit FunctionEnd IfTrimNulls = Left(strWork, pos)End FunctionVB.NETLevel: Beginning—Andy Clark, Richmond, Va.Clarify Procedure Attributes With Line ContinuationAdd readability to your attribute assignments by placing them ontheir own line with an underscore line-continuation character: _Public Sub Calc()...End SubVB.NET, C#Level: BeginningReading Console Output When Working in the IDEWhen you run a console from the IDE, the console often disappearsbefore you get the chance to view the output. You can work aroundthis by using Console.Read() to pause the program until you hitthe Enter key.VB.NET:C#:Console.WriteLine("Press Enter to close this window")Console.Read()Console.WriteLine("Press Enter to close this window");Console.Read()You can also change the build output type to Windows Application.When you change a console application’s output type toWindows Application, the console’s output gets redirected tothe IDE’s output window where you can view the output afterthe application has finished running as well as while the applicationis running.SQL Server 6.5 and upLevel: Beginning—Jonathan Goodyear, Orlando, Fla.Find the Cause of Query MalfunctionsWhen debugging a SELECT query, add an absolute true conditionas the first condition of the WHERE clause:SELECTau_lname,au_fnameFROMauthorsWHERE1=1 --absolute true conditionand state = 'CA'and contract = 1That way, you can comment out one or more of the real conditionsin the WHERE clause using the “- -” comment character sequence tonarrow down which condition(s) cause the query to malfunction.When you’re done debugging your query, remove the absolute truecondition as well as the “and” in front of the first real condition.VB.NETLevel: Beginning—Jonathan Goodyear, Orlando, Fla.Know the Differences Between CStr and .ToStringVB.NET’s CStr() method is locale-aware, which means it uses thelocale at run time to determine how to format the string. The.ToString method is locale-neutral and is generally quicker. If youneed to format strings according to the end user’s regional settings,use CStr. Otherwise, use .ToString, which works on allobjects. CStr works only on objects that implement IFormattable.Note: Neither works exactly like VB5/6 CStr, which returns alocalized string based on compile-time settings.—Bill McCarthy, Barongarook, Victoria, Australia—Jonathan Goodyear, Orlando, Fla.SEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 7


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVBS, ASP/IIS 4.0 and upLevel: IntermediateUpload Files to Active Server PagesMany companies sell ActiveX objects for uploading files to ActiveServer Pages. However, you can easily write a bit of VBScript to handlethe uploads yourself. The Request object has all the uploaded data:


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB3, VB4, VB5, VB6Level: BeginningLoop Using GetTickCount or timeGetTimeThis TickDiff function returns the difference of two calls toGetTickCount or timeGetTime, taking into account things such asVB’s two’s complement as well as wrapping by the operatingsystem:#If Win32 ThenPrivate Declare Function GetTickCount _Lib "kernel32" () As Long#ElsePrivate Declare Function GetTickCount _Lib "User" () As Long#End IfPublic Sub SampleLoop()Dim TickStart As CurrencyTickStart = GetTickCount()' Loop for 5 secondsDo While TickDiff(TickStart, GetTickCount()) _< 5000' loop code hereLoopEnd SubPublic Function TickDiff( _ByVal TickStart As Currency, _ByVal TickEnd As Currency) As Long' CCur(2 ^ 32)Const TwoToThe32nd As Currency = 4294967296@' Handle two's complement for values larger than' 2147483647&If TickStart < 0 ThenTickStart = TickStart + TwoToThe32ndEnd If' Handle two's complement AND the case where' timeGetTime/GetTickCount wraps at (2 ^ 32)ms,' or ~49.7 days:If (TickEnd < 0) Or (TickEnd < TickStart) ThenTickEnd = TickEnd + TwoToThe32ndEnd If' Return the resultTickDiff = TickEnd - TickStartEnd FunctionVS.NETLevel: Beginning—Monte Hansen, Ripon, Calif.Work With Miscellaneous Files<strong>Visual</strong> <strong>Studio</strong>.NET has a miscellaneous files feature that allows youto create links to other files in Solution Explorer. The files are notadded to your project or copied to the project folder. This comesin handy if you want to refer to some reference document whileworking on a project, such as a readme file, or when you’re writingAPI declarations. You can use the Find in Files features of VS.NETto locate the .h (C/C++ Header file) that has the functions andconstants declared. Double-click on the file in the results to add itto your miscellaneous files.The miscellaneous files will contain links to them from SolutionExplorer the next time you open the project. To enable this, youneed to turn on the miscellaneous files option and set the limit forhow many files should be remembered per solution. To do this,you select Tools | Options | Environment | Documents and checkthe Show Miscellaneous Files feature. Then type in the number offiles to remember (between 0 and 256 per solution).VB6, SQL Server 7.0 and upLevel: AdvancedPrevent SQL Server From Returning Record CountsIf you’re calling a stored procedure from a Microsoft TransactionServer (MTS) transaction and that stored procedure performsseveral actions before completing, SQL Server returns a count ofaffected records for each action. If an error occurs after the firstrecord count is returned, the MTS transaction won’t acknowledgethe error because it sees the record count returned as a successmessage. To alleviate this problem, put “SET NOCOUNT ON” at thetop of the stored procedure. Setting this option prevents SQLServer from returning record counts.This example will not return an error to an MTS transaction:USE NorthwindGOCREATE PROCEDURE sp_TestMTSNoErrorAS/*** This query returns a count of affected record equal to 1.*/UPDATE OrdersSET EmployeeID = 5WHERE OrderID = 10248/*** This query generates a 'Divide by Zero' error.*/SELECT 1/0GO/*** Running this in Query Analyzer will show an error.*/EXECUTE sp_TestMTSNoErrorReturns:(1 row(s) affected)Server: Msg 8134, Level 16, State 1, Proceduresp_TestMTSNoError, Line 16Divide by zero error encountered.An MTS transaction won’t see the error message returned by thisquery unless you apply the “SET NOCOUNT ON” option.VB4, VB5, VB6Level: Beginning—Jason Rein, Thompson’s Station, Tenn.Tiling Made EasyUse this method whenever you need a tiled background on yourforms. All you need is an Image control with its Visible property setto False, and a graphic in its Picture property. Set the form’sAutoRedraw property to False and place this code inside theForm_Paint event:Private Sub Form_Paint()Dim X As SingleDim Y As SingleFor Y = 0 To Me.ScaleHeight Step Image1.HeightFor X = 0 To Me.ScaleWidth Step Image1.WidthMe.PaintPicture Image1.Picture, X, YNext XNext YEnd SubThis code tiles within a PictureBox control as well. Simply replaceMe with the name of the PictureBox.—Brian McDonald, Covington, Ky.—Bill McCarthy, Barongarook, Victoria, AustraliaSEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 9


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersXML, .NET beta 1 and upLevel: Beginning✰✰✰✰✰ Five Star TipVS.NETLevel: BeginningRead XML Documents EfficientlyIn applications where you need to read XML documents in the mostefficient manner possible, consider using the XmlTextReader classrather than the XmlDocument class. The XmlTextReader is a streamthat allows tokens found in the source XML document to be read ina forward-only manner without loading the entire XML documentinto memory as with the DOM. Although at first glance this mightseem similar to Simple API for XML (SAX), the XmlTextReaderexposes a pull model rather than the push model found in SAX. Thepull model offers many performance benefits. Using theXmlTextReader in an ASPX page is as simple as calling the read()method (you must reference the System.Xml namespace):XmlTextReader reader = newXmlTextReader(Server.MapPath("myfile.xml"));while (reader.Read()) {if (reader.NodeType == XmlNodeType.Element) {Response.Write("Found an Element!");if (reader.HasAttributes()) {while (reader.MoveToNextAttribute()) {Response.Write("&nbsp;&nbsp;Found an Attribute!");}}}}// Make sure you close the stream to prevent file lockingreader.Close();VB5, VB6, SQL Server 7.0 and upLevel: Intermediate—Dan Wahlin, Chandler, Ariz.Execute a Temporary SQL Stored ProcedureIf a user doesn’t have permission to create a stored procedure inSQL Server (version 7.0 or later), he or she can still use ADO tocreate a temporary stored procedure and execute it from VB:Dim cmd As ADODB.CommandDim conn As ADODB.ConnectionSet conn = New ADODB.ConnectionSet cmd = New ADODB.Command' Replace the connection string values below as' required.conn.Open "Provider=SQLOLEDB;Data" & _" <strong>Source</strong>=MyData<strong>Source</strong>;Initial" & _" Catalog=InitialCatalog; User" & _" ID=userID;Password=password"strCmd = "SELECT Customers.CustomerID, OrderID," & _" ContactName INTO #tempTable FROM Customers" & _" INNER Join Orders ON Orders.CustomerID =" & _" Customers.CustomerID"cmd.ActiveConnection = conncmd.CommandText = strCmdcmd.CommandType = adCmdTextcmd.Prepared = Truecmd.ExecuteUsing the Prepared property causes SQL Server to create andstore a temporary stored procedure in the tempdb database, in atechnique known as Prepared/Execute. The command text cancontain most things you could put in a SQL stored procedure.—Parthasarathy Mandayam, Bellevue, Wash.Use #region and #endregion to Organize CodeThe #region and #endregion preprocessor directives define blocksof code you can expand or collapse in the <strong>Visual</strong> <strong>Studio</strong> editor:#region MenuHandlersprivate void OnNew (object sender, EventArgs e){Invalidate ();}private void OnExit (object sender, EventArgs e){Close ();}private void OnOpen (object sender, EventArgs e){Close ();}private void OnSave (object sender, EventArgs e){Close ();}#endregionThis code defines a region called MenuHandlers. You can expandor collapse this node in <strong>Visual</strong> <strong>Studio</strong> using the <strong>Visual</strong> <strong>Studio</strong> EditorOutline feature. This feature lets you show only the code you’reworking with; it hides the rest in a class.VB4, VB5, VB6Level: Intermediate—Bill Wagner, Manchester, Mich.Déjà QueueI especially enjoyed the “Quick and Easy Queue” tip in the 11thedition of “<strong>101</strong> <strong>Tech</strong> <strong>Tips</strong> for VB Developers” [<strong>Visual</strong> BasicProgrammer’s Journal March 2001]. You can use the technique for an“undo” menu/toolbar option. Use a collection instead of a listbox toreduce overhead. Support for both LIFO and FIFO is also included:Public Queue As New CollectionPublic Const Q_LIFO = 0Public Const Q_FIFO = 1Public Sub Enqueue(QueueItem As Variant)Queue.Add QueueItemEnd SubPublic Function Dequeue(Optional Mode As Long) As VariantDim Position as LongIf Queue.Count > 0 ThenIf Mode = Q_LIFO ThenPosition = Queue.CountElsePosition = 1End IfDequeue = Queue(Position)Queue.Remove PositionElseDequeue = NullEnd IfEnd Function—Brian Ray, Rockford, Ill.10 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB6Level: AdvancedOpen a ToolBar Dropdown MenuVB6 introduced a new Style=5-tbrDropDown of the Button objectfor the ToolBar control. In this case, you can add one or moreButtonMenu objects to the current Button. Unfortunately, youcan only open the dropdown menu by clicking on the dropdownarrow; in other words, it has no built-in functionality foropening a dropdown menu from code. Here’s one function,ShowPopUpMenu, that lets you open a dropdown menu fromany place in your source code:Private Type POINTAPIx As Longy As LongEnd TypePrivate Declare Function GetCursorPos Lib "user32" _(lpPoint As POINTAPI) As LongPrivate Declare Function ClientToScreen Lib _"user32" (ByVal hWnd As Long, _lpPoint As POINTAPI) As LongPrivate Declare Function SetCursorPos Lib "user32" _(ByVal x As Long, ByVal y As Long) As LongPrivate Declare Function ShowCursor Lib "user32" _(ByVal bShow As Long) As LongPrivate Declare Sub mouse_event Lib "user32" ( _ByVal dwFlags As Long, ByVal dx As Long, _ByVal dy As Long, ByVal cButtons As Long, _ByVal dwExtraInfo As Long)Private Sub ShowPopUpMenu(TB As Toolbar, _IndexOfButton%)Const MOUSEEVENTF_LEFTDOWN = &H2Const MOUSEEVENTF_LEFTUP = &H4Dim Pt As POINTAPI, oldPt As POINTAPIWith TB.Buttons(IndexOfButton)If Not (.Style = tbrDropdown) Then Exit SubCall GetCursorPos(oldPt)Call ClientToScreen(TB.hWnd, Pt)Call ShowCursor(False)Call SetCursorPos(Pt.x + ((.Left + .Width) / _Screen.TwipsPerPixelX) - 1, _Pt.y + ((.Top + .Height \ 2) / _Screen.TwipsPerPixelY))End WithCall mouse_event(MOUSEEVENTF_LEFTDOWN, _0, 0, 0, 0)Call mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)Call SetCursorPos(oldPt.x, oldPt.y)Call ShowCursor(True)End SubFor example, if you need to open a dropdown menu when a userclicks on a main part of any button, use this source code:Sub ToolBar1_ButtonClick(ByVal Button As _MSComctlLib.Button)Call ShowPopUpMenu(ToolBar1, Button.Index)End Sub—Vladimir Olifer, Staten Island, N.Y.VB3, VB4, VB5, VB6Level: IntermediateReplicate Character PatternsVB’s String function is useful to fill a large string with a specificcharacter. Occasionally, you might need to fill a string with arepeating set of characters. If you’re using a small database heldinside a string, for example, you might want to set default valuesfor some of the fields.When the need does arise, you can use VB’s Mid statement tohandle the task:Dim Data As StringConst Rep = "ABCD"Data = Rep & Space$(1000)Mid$(Data, Len(Rep) + 1) = DataThese last two statements do a significant amount of work. The firstline allocates the required memory, and the second fills that memorywith character data—that’s pretty good for only two lines of code.Not surprisingly, this step is quick, even for large strings, and itprovides better functionality than VB’s regular String function:Public Function Replicate (ByVal Number As Long, _ByVal Pattern As String) As String' Returns PATTERN replicated in a string NUMBER times.' Number = Number of replications desired' Pattern = Character pattern to replicateDim LP As LongDim sRet As StringIf Number > 0 ThenLP = Len(Pattern)If LP > 1 ThensRet = Pattern & Space$((Number - 1) * LP)If Number > 1 ThenMid$(sRet, LP + 1) = sRetEnd IfElsesRet = String$(Number, Pattern)End IfEnd IfReplicate = sRetEnd FunctionVS.NETLevel: Intermediate—Larry Serflaten, Monticello, Minn.Examine ILWhen you compile a VB.NET or C# project, the code is compiled toMicrosoft Intermediate Language (MSIL). You can view the IL codein ILDasm.exe. Launch ILDASM from the VS.NET IDE to view thecompiled IL for your code quickly. First, ensure that ILDASM isinstalled—it should be located in the .NET Framework tools directory.If it is not in there, run Setup again and make sure you installthe .NET SDK components. Select External Tools from the Toolsmenu. Add an entry for ILDASM and specify these parameters:Command: the full path to ildasm.exeArguments: $(TargetPath)When you want to look at the IL for your program, simply build itand click on your ILDASM entry in the Tools menu. To dump the ILto a text file, specify this parameter:Arguments:$(TargetPath) /out=$(TargetDir)$(TargetName).il—Bill McCarthy, Barongarook, Victoria, AustraliaSEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 11


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVB3, VB4, VB5, VB6Level: AdvancedBoundless Array IndexingUse this function for getting the 10th next element from an arraywith “circular” contents such as “Monday,” “Tuesday,” and so on:intCurrent = WrapIndex(Index:=intCurrent, _Move:=+10, UpperBound:=6)You don’t have to think about passing the end of the array as longas that’s what you want to do:Public Function WrapIndex(ByVal Index As Long, _ByVal Move As Long, _ByVal UpperBound As Long, _Optional ByVal LowerBound As Long) As Long' Function for incrementing an index past UB' and starting over from LB, or the other way' around.' If LowerBound is omitted, 0-base is assumed.Dim lngCount As LongIf UpperBound = LowerBound ThenWrapIndex = LowerBoundElse' Swap UpperBound and LowerBound if neededIf LowerBound > UpperBound ThenLowerBound = LowerBound Xor UpperBoundUpperBound = LowerBound Xor UpperBoundLowerBound = LowerBound Xor UpperBoundEnd If' number of elements in rangelngCount = UpperBound - LowerBound + 1' Move Index inside of range' LowerBound...UpperBound if neededIf Index < LowerBound ThenIndex = UpperBound - ((LowerBound -_Index) Mod lngCount) + 1ElseIf Index > UpperBound ThenIndex = LowerBound + ((Index -_UpperBound) Mod lngCount) - 1End If' Move to the new indexSelect Case MoveCase Is > 0WrapIndex = (Index - LowerBound + _Move) Mod lngCount + LowerBoundCase Is < 0WrapIndex = (Index - LowerBound + _lngCount - Abs(Move Mod lngCount)) _Mod lngCount + LowerBoundCase 0WrapIndex = IndexEnd SelectEnd IfEnd Function—André Lomøy, Oslo, NorwayVB4/32, VB5, VB6, VBS, SQL 7.0 and upLevel: IntermediateAvoid Zero-Length String Parameter FailuresHave you ever had a zero-length string parameter fail when attemptingto execute a stored procedure from ADO? You’ll find thiswarning buried in the ADO documentation under the Appendmethod for the Parameters collection: “If you select a variablelengthdata type, you must also set the Size property to a valuegreater than zero.” This refers to any parameter passed as typeadLongVarChar, adLongVarWChar, adVarChar, or adVarWChar. Iuse adVarWChar to send a parameter to a SQL Server storedprocedure expecting a varchar(N), so I’ve had problems with zerolengthstrings. If you pass a zero-length string and use VB’s Lenfunction to retrieve its length, an error results when the length ispassed as 0.I wrote a simple function, LenStringParameter, to return alength of 1 instead. Place the function in a module to make itavailable from anywhere in your app:Function LenStringParameter(strParam As String) As Long' From the ADO BOL: "If you select a variable-length' data type, you must also set the Size property to a' value greater than zero."' The length must be passed as 1 even if the string' is empty or Null.LenStringParameter = IIf(Len(strParam) = 0, 1, _Len(strParam))End FunctionTest this function by substituting LenStringParameter for Lenwherever needed:Public Function TestStringParameter() as StringDim strMyTestValue As StringDim intOtherValue As IntegerstrMyTestValue = TextMyKeyValue.Text' Read from a text field, or assign directly.intOtherValue = 1Dim cmdADO As ADODB.Command: Set cmdADO = _New ADODB.CommandWith cmdADO.ActiveConnection = strConnection' Your connection string or connection here..CommandType = adCmdStoredProc.CommandText = "spReturnMyAnswer".Parameters.Append .CreateParameter("MyTestValue", _adVarWChar, adParamInputOutput, _LenStringParameter(strMyTestValue), _strMyTestValue).Parameters.Append .CreateParameter("OtherValue", _adInteger, adParamInputOutput, _Len(intOtherValue), intOtherValue).Execute' Pick up the return values.strMyTestValue = .Parameters("MyTestValue").ValueintOtherValue = .Parameters("OtherValue").ValueEnd WithSet cmdADO = NothingTestStringParameter = strMyTestValueEnd FunctionA word of caution: If you expect an adParamInputOutput typeparameter’s return value to be larger than the size going in, don’t useLenStringParameter. You’ll receive truncated data. In other words,if LenStringParameter returns 20, and 20 is sent as the size of anadParamInputOutput type parameter, a maximum of 20 characterswill be returned, even if the stored procedure sets the value of theOUTPUT parameter to a value longer than 20 characters. Instead,set the size to the maximum allowed by the stored procedure. If thestored procedure expects a varchar(25) OUTPUT, send the lengthas 25. If it returns less than 25, the extra space will be discarded.—Jake Mireles, Houston12 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB4/32, VB5, VB6Level: IntermediateConvert a VB CommandButton Into a Picture Buttonat Run TimeI first used this function in the VB4 days when CommandButtonsdidn’t have a graphical Style property. It’s still useful because youcan’t set the Style property at run time. You can use this techniqueto produce many different styles of intrinsic controls:Private Declare Function GetWindowLong Lib "user32" _Alias "GetWindowLongA" (ByVal hWnd As Long, _ByVal nIndex As Long) As LongPrivate Declare Function SetWindowLong Lib "user32" _Alias "SetWindowLongA" (ByVal hWnd As Long, _ByVal nIndex As Long, ByVal dwNewLong As Long) _As LongPrivate Declare Function SendMessage Lib "user32" _Alias "SendMessageA" (ByVal hWnd As Long, _ByVal wMsg As Long, ByVal wParam As Long, _ByVal lParam As Long) As LongPrivate Const BS_BITMAP As Long = &H80&Private Const BS_ICON As Long = &H40&Private Const BS_TEXT As Long = 0&Private Const BM_GETIMAGE As Long = &HF6Private Const BM_SETIMAGE As Long = &HF7Private Const IMAGE_BITMAP As Long = 0&Private Const IMAGE_ICON As Long = 1&Private Const GWL_STYLE As Long = (-16&)Private Const GWL_EXSTYLE As Long = (-20&)Public Sub SetButtonGlyph(Button As CommandButton, _Picture As StdPicture)Dim dwStyle As LongDim hImage As LongDim ImageType As Long' Get the button styledwStyle = GetWindowLong(Button.hWnd, GWL_STYLE)If Picture Is Nothing Then' Clear the graphic style, add the text styledwStyle = (dwStyle Or BS_TEXT) And _Not (BS_BITMAP Or BS_ICON)If (dwStyle And BS_BITMAP) 0 ThenCall SendMessage(Button.hWnd, _BM_SETIMAGE, IMAGE_BITMAP, 0&)ElseIf (dwStyle And BS_ICON) 0 ThenCall SendMessage(Button.hWnd, _BM_SETIMAGE, IMAGE_ICON, 0&)End If' Update style bits & redrawSetWindowLong Button.hWnd, GWL_STYLE, dwStyleButton.RefreshElse' Remove mutually exclusive bitsdwStyle = dwStyle And _Not (BS_BITMAP Or BS_ICON Or BS_TEXT)Select Case Picture.TypeCase vbPicTypeIcondwStyle = dwStyle Or BS_ICONImageType = IMAGE_ICONCase vbPicTypeBitmapdwStyle = dwStyle Or BS_BITMAPImageType = IMAGE_BITMAPEnd Select' Handle of image to attach to button.hImage = Picture.Handle' Change the style of the buttonCall SetWindowLong(Button.hWnd, GWL_STYLE, _dwStyle)' Add or remove the glyphCall SendMessage(Button.hWnd, BM_SETIMAGE, _ImageType, hImage)End IfEnd SubVB3, VB4, VB5, VB6Level: Beginning—Monte Hansen, Ripon, Calif.Object Properties as Parameters are ByVal OnlyIn VB, we can use a function/sub call to return results by passingparameters by reference (although it’s generally a bad idea). Beaware that if you use an object’s properties as parameters, theresult might not be what you expected. For example, create asimple form application with a textbox and command button, thenrun this:Private Sub Command1_Click()Dim szMsg As StringszMsg = "Before: """ & Text1.Text & """" & vbCrLfCall testStr(Text1.Text)szMsg = szMsg & "After: """ & Text1.Text & """"MsgBox szMsgEnd SubPrivate Sub testStr(aszText As String)aszText = "Text string changed"End SubIf you use an object’s property directly as a parameter, thatproperty won’t be updated. The reason is simple: VB copies theproperty into a temporary memory location and passes thatmemory as the parameter into the function/sub. VB doesn’t copythe memory back to the object’s property after the call, so anychanges you make are lost.VB3, VB4, VB5, VB6Level: Beginning—David Chu, Calgary, AlbertaApp.Path is InconsistentThe path returned by App.Path is inconsistent. If the program isrunning in a root directory, the path will have a backslash on theend. Otherwise, it won’t. The solution is to write one or twowrapper functions to ensure a path has a backslash on the end:Public Function NormalizePath( _ByVal strPath As String) As String' If the path doesn't have a slash at the end,' add one.If Right$(strPath, 1) = "\" ThenNormalizePath = strPath & "\"ElseNormalizePath = strPathEnd IfEnd FunctionPublic Function AppPath() As String' Return the normalized App.Path ...AppPath = NormalizePath(App.Path)End FunctionNow you get a consistent App.Path easily by calling the AppPathfunction.—Chris Hynes, Fort Washington, Md.SEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 13


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVB.NETLevel: IntermediateUse Int16, Int32, and Int64 for API DeclarationsWhen declaring API functions in VB.NET, use Int16, Int32, and Int64rather than Short, Integer, and Long, respectively, to avoid possibleconfusion with VB6 declarations. Note: In VB6, Long is 32-bitand Integer is 16-bit, whereas in VB.NET, Long is 64-bit and Integeris 32-bit.VB5, VB6Level: Intermediate—Bill McCarthy, Barongarook, Victoria, AustraliaMake the Background of YourRichTextBox Controls TransparentIf you plan to require Windows 2000 for your application, you canmake your standard VB RichTextBox control 100-percent transparentwith a few simple API calls. To try this tip, create a newproject (or use an existing one), add a RichTextBox control, andadd this code and these declarations in a standard module:Option Explicit' Win32 APIs.Private Declare Function GetWindowLong _Lib "user32" Alias "GetWindowLongA" _(ByVal hWnd As Long, _ByVal nIndex As Long) As LongPrivate Declare Function SetWindowLong _Lib "user32" Alias "SetWindowLongA" _(ByVal hWnd As Long, ByVal nIndex As Long, _ByVal dwNewLong As Long) As LongPrivate Declare Function SetWindowPos Lib "user32" _(ByVal hWnd As Long, ByVal hWndInsertAfter As _Long, ByVal X As Long, ByVal Y As Long, _ByVal cx As Long, ByVal cy As Long, _ByVal wFlags As Long) As Long' Style bits.Private Const GWL_EXSTYLE As Long = (-20)Private Const WS_EX_TRANSPARENT As Long = &H20' Force total redraw that shows new styles.Private Const SWP_FRAMECHANGED = &H20Private Const SWP_NOMOVE = &H2Private Const SWP_NOZORDER = &H4Private Const SWP_NOSIZE = &H1Public Function Transparent(ByVal hWnd As Long, _Optional ByVal Value As Boolean = True) As _BooleanDim nStyle As LongConst swpFlags As Long = _SWP_FRAMECHANGED Or SWP_NOMOVE Or _SWP_NOZORDER Or SWP_NOSIZE' Get current style bits.nStyle = GetWindowLong(hWnd, GWL_EXSTYLE)' Set new bits as desired.If Value ThennStyle = nStyle Or WS_EX_TRANSPARENTElsenStyle = nStyle And Not WS_EX_TRANSPARENTEnd IfCall SetWindowLong(hWnd, GWL_EXSTYLE, nStyle)' Force redraw using new bits.SetWindowPos hWnd, 0, 0, 0, 0, 0, swpFlags' Make sure new style took.Transparent = _(GetWindowLong(hWnd, GWL_EXSTYLE) = nStyle)End FunctionYou can use this function to toggle the transparency of yourRichTextBox controls at will:Private Sub Check1_Click()Call Transparent(RichTextBox1.hWnd, _(Check1.Value = vbChecked))End SubTo be on the safe side, check the OS version before makingthese calls, as the effects can be rather unpleasant in thewrong environment.That’s it! A simple call to Get/SetWindowLong retrieves thecurrent extended style bits and adds the standard TRANSPARENTstyle so the window becomes transparent. Note, if you change thestyle after the control is visible, you need to force the screen torepaint to see the effect.VB4, VB5, VB6Level: Beginning—John Cullen, Pedroucos, PortugalDuck the Modal Form PopupMenu BugMicrosoft confirms this bug: If an application contains at least twoforms, and one of those forms is displayed modally using aPopupMenu on another form, a PopupMenu on the modal formwon’t be displayed. Knowledge Base article Q167839 - BUG:PopupMenu on Modal Form Not Displayed suggests using a Timercontrol as a workaround for this problem. My solution sets a flagvariable in the first form’s menu procedure, which is then actedupon after the popup is dismissed.Start a new Standard EXE project. Form1 is added by default.Add another form (Form2) to the project. On Form1, create aninvisible menu (mnuFile) with the caption “File” that has a submenu(mnuOpen) with the caption “Open”. On Form2, create an invisiblemenu (mnuEdit) with the caption “Edit” that has a submenu(mnuFind) with the caption “Find”. Then add this code to Form1:Private bShowForm2 as BooleanPrivate Sub Form_Click()PopupMenu mnuFileIf bShowForm2 ThenbShowForm2 = FalseForm2.Show vbModalEnd IfEnd SubPrivate Sub mnuOpen_Click()bShowForm2 = TrueEnd SubAdd this code to Form2:Private Sub Form_Click()PopupMenu mnuEditEnd SubPress F5 to run the program. Click on Form1 to display the FilePopupMenu. Select Open to show Form2 modally. Click on Form2to display the Edit PopupMenu.—Peter Gabris, Marietta, Ga.14 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB3, VB4, VB5, VB6Level: BeginningOpen Namespace Objects From VBAs you probably know, you can open an Explorer window on adirectory from VB by using the intrinsic Shell function:Dim TaskId As LongTaskId = Shell("Explorer c:\", vbNormalFocus)But what about items in the namespace that don’t correspond tophysical file system objects, such as the Printers and Task Schedulerfolders—or My Computer itself, for that matter?Here’s a little-known trick: You can pass an argument to Explorerindicating the namespace object by its GUID, and Explorerdutifully opens it for you. For example, to open Scheduled Tasks,you could use this:Dim TaskId As LongDim ShellCmd As StringShellCmd = "Explorer ::{20D04FE0-" & _"3AEA-1069-A2D8-08002B30309D}" & _"\::{D6277990-4C6A-11CF-8D87-" & _"00AA0060F5BF}"TaskId = Shell(ShellCmd, vbNormalFocus)Here’s a list of the most common namespace objects and theequivalent “paths” to pass to Explorer in the Shell command; thisinformation comes straight from the Windows Registry, which youcan also search for other namespace objects. Now you can openany of these objects right from within VB:My Computer::{20D04FE0-3AEA-1069-A2D8-08002B30309D}Network Neighborhood::{208D2C60-3AEA-1069-A2D7-08002B30309D}Recycle Bin::{645FF040-5081-<strong>101</strong>B-9F08-00AA002F954E}Task Scheduler::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{D6277990-4C6A-11CF-8D87-00AA0060F5BF}Printers::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{2227A280-3AEA-1069-A2DE-08002B30309D}Control Panel::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{21EC2020-3AEA-1069-A2DD-08002B30309D}Dial-up Networking::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{a4d92740-67cd-11cf-96f2-00aa00a11dd9}Web Folders::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{BDEADF00-C265-11D0-BCED-00A0C90AB50F}VS.NETLevel: Beginning—Jason Fisher, DallasCustomize Toolbox IconsYou can create your own custom bitmap to give your UserControlor component its own unique toolbox icon. Simply add a 16-by-16bitmap to your project and give it the same name as the component;for example, MyComponent.bmp. Set its Build action toEmbedded Resource.—Bill McCarthy, Barongarook, Victoria, AustraliaVB3, VB4, VB5, VB6Level: BeginningKeep Selected Areas in Grid Read-OnlyI needed to lock a row on a grid to show totals as well as apercentage row that needed to remain read-only to the user. Ididn’t want to add another grid with a single row for totals as itdidn’t have the flexibility I needed. After some Web searching, Ifound this to be a common issue. After much hair-pulling and manycaffeinated beverages, I discovered this simple and basic answerin one line of code. Enjoy:Private Sub MyDBGrid_KeyPress(KeyAscii As Integer)' Whatever row you want to be "locked"If MyDBGrid.Row = MyTotalsRow Then KeyAscii = 0End SubVB6, VBSLevel: Beginning—Joe Johnston, Chesapeake, Va.Quick SplitWhen you use the Split function from VB6 or VBScript, sometimesyou need only a single value and not the whole array. To do this,you can reference the element you need right after the Splitstatement like this:Split(myVar, myDelim)(1)This statement retrieves the second element of the array, element1.VB4, VB5, VB6Level: Beginning—Judah Reeves, San DiegoZoom Continuously in Your Image-Processing AppsThis code demonstrates how fast you can zoom into images usingVB’s form PaintPicture method. Start a new project containing twoforms named frmClip and frmPicture. frmPicture contains a Shapecontrol named shpRectangle, an Image control named pic, and allthe code. frmClip contains no code, but it’s the target of theclipped image within shpRectangle as it is dragged over pic:' frmPictureOption ExplicitPrivate mlTop As LongPrivate mlLeft As LongPrivate mlRight As LongPrivate mlBottom As LongPrivate Sub Form_Load()Me.ScaleMode = vbTwipsWith pic.BorderStyle = 0 ' none.Move 0, 0.Picture = LoadPicture( _"C:\Anderson\Imaging\Images\address.bmp").ZOrder vbSendToBackEnd WithWith shpRectangle.Shape = 0 ' Rectangle.BorderStyle = 1 ' solid.BorderWidth = 2.DrawMode = 13 'Copy pen.Visible = FalseEnd WithfrmClip.ScaleMode = vbTwipsfrmClip.ShowEnd SubPrivate Sub pic_MouseMove(Button As Integer, _SEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 15


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersShift As Integer, X As Single, Y As Single)If Button = vbLeftButton ThenWith shpRectangle.Visible = False 'reduces flickeringmlBottom = YmlRight = X' Don't allow clip to include non-picture' regionIf mlBottom > pic.Height Then _mlBottom = pic.HeightIf mlRight > pic.Width Then _mlRight = pic.WidthIf mlBottom < pic.Top Then _mlBottom = pic.TopIf mlRight < pic.Left Then _mlRight = pic.Left' Swap top/bottom as necessaryIf mlBottom < mlTop Then.Top = mlBottom.Height = mlTop - mlBottomElse.Top = mlTop.Height = mlBottom - mlTopEnd If' Swap left/right as necessaryIf mlRight < mlLeft Then.Left = mlRight.Width = mlLeft - mlRightElse.Left = mlLeft.Width = mlRight - mlLeftEnd If.Visible = TrueDoEvents ' Allow rectangle to drawfrmClip.PaintPicture pic.Picture, 0, 0, _frmClip.ScaleWidth, _frmClip.ScaleHeight, _.Left - pic.Left, .Top - pic.Top, _.Width, .HeightEnd WithEnd IfEnd SubPrivate Sub pic_MouseDown(Button As Integer, _Shift As Integer, X As Single, Y As Single)If Button = vbLeftButton ThenmlTop = YmlLeft = XEnd IfEnd SubPrivate Sub pic_MouseUp(Button As Integer, _Shift As Integer, X As Single, Y As Single)shpRectangle.Visible = FalseEnd Sub—Graeme Anderson, Blackburn, AustraliaVB5, VB6Level: IntermediateMerge VBL Files Into Your RegistryUse this tip when developing ActiveX components and testingActiveX OCXs that require license files. These Registry entriesallow you to merge VBL file contents into the Registry. You addthree context menu options for VBL files: Merge (into the Registry),Edit, and Print:REGEDIT4[HKEY_CLASSES_ROOT\.vbl]@="<strong>Visual</strong>Basic.VBLFile"[HKEY_CLASSES_ROOT\<strong>Visual</strong>Basic.VBLFile]@="<strong>Visual</strong> Basic Control License File"[HKEY_CLASSES_ROOT\<strong>Visual</strong>Basic.VBLFile\DefaultIcon]@="NOTEPAD.EXE,1"[HKEY_CLASSES_ROOT\<strong>Visual</strong>Basic.VBLFile\shell][HKEY_CLASSES_ROOT\<strong>Visual</strong>Basic.VBLFile\shell\edit]@="&Edit"[HKEY_CLASSES_ROOT\<strong>Visual</strong>Basic.VBLFile\shell\edit\command]@="NOTEPAD.EXE \"%1\""[HKEY_CLASSES_ROOT\<strong>Visual</strong>Basic.VBLFile\shell\open]@="Mer&ge"[HKEY_CLASSES_ROOT\<strong>Visual</strong>Basic.VBLFile\shell\open\command]@="regedit.exe \"%1\""[HKEY_CLASSES_ROOT\<strong>Visual</strong>Basic.VBLFile\shell\print][HKEY_CLASSES_ROOT\<strong>Visual</strong>Basic.VBLFile\shell\print\command]@="NOTEPAD.EXE /P \"%1\""Editor’s Note: All the usual warnings apply, of course, when runningscripts against your Registry.VB4, VB5, VB6Level: Beginning—Tom Sweet, Marietta, Ga.Use the Keyboard for Extra-Fine Sizing andPositioningIf you have a sensitive mouse and/or many objects you need toposition carefully on a form, getting everything right can be a realpain—especially when you click on an object simply to alter someof its properties and move it accidentally. Here’s the answer: Usethe “Lock Controls” option under the Format menu to lock theposition of a form’s controls so you can’t change control positionsaccidentally when clicking on the controls. But now you can’t usethe mouse to reposition a control without unlocking everything.However, you can use the keyboard. Simply select the controlwhose position you want to change and combine the Ctrl key andarrow keys to move the control, or the Shift key and arrow keys toresize it. The grid spacing you’ve configured (Tools | Options |General) controls the move/size extent per arrow press.—John Cullen, Pedroucos, Portugal16 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB4, VB5, VB6Level: IntermediateOverride the Err ObjectBelieve it or not, you can override VB’s built-in Err object to addfunctionality the Err object doesn’t offer. Create a class calledCError, then add this property procedure:Public Property Get Number() As LongNumber = VBA.Err.NumberEnd PropertyNow create a BAS module and add this code:Public Function Err() As CErrorStatic oErr As CErrorIf oErr Is Nothing ThenSet oErr = New CErrorEnd IfSet Err = oErrEnd FunctionNow you can retrieve the custom CError object anywhere in yourproject that you reference the Err object. Of course, you want toadd support for standard properties and methods such as Description,<strong>Source</strong>, Raise, and so on. You can also add support forother things:• Err.Line (could return the Erl)• Err.Log (could log the error to a text file or the event log)• Err.FullDescription (could wrap the number, source, and descriptioninto one nicely formatted string)• Err.Stack (could return stack trace information)• Err.Show (could display a nicely formatted message box describingthe error)The nice thing about this approach is that it consolidates all yourerror-handling code neatly into what appears to be the Err objectitself.VB5, VB6Level: Beginning—Darin Higgins, Fort Worth, TexasCopy an Array Faster, ReduxSimple is usually best. The “Copy an Array Faster” tip in the 11thEdition of “<strong>101</strong> <strong>Tech</strong> <strong>Tips</strong> for VB Developers” [<strong>Visual</strong> BasicProgrammer’s Journal March 2001] describes a method that uses alow-level approach to accelerate array copying. VB4 introduceddirect assignment to Byte arrays, and VB5 later expanded thatcapability to include other array types as well.This code does the same thing as the previous tip, with thesame performance improvement:Dim IntArray1() As IntegerDim IntArray2() As IntegerReDim IntArray1(1 To 6000000)ReDim IntArray2(1 To 6000000)IntArray2 = IntArray1As the old saying goes: “Keep it simple, stupid.” The previous tipis still useful for copying portions of arrays, but use direct assignmentif you need to copy the entire array.—Dave Doknjas, Surrey, British ColumbiaVB5, VB6Level: IntermediateSort Arrays FasterWhen an array is declared as a type where each element occupiesa lot of memory (such as a complex user-defined type), sorting thearray can become unacceptably slow. To speed things up, the datacontained within the array shouldn’t be moved in memory anymore than necessary.To achieve this, you can set up a second array of integers thatcontains the indexes of the main array. Your sorting algorithmchanges the order of the index array based on comparisonswithin the main array. When the process is complete, the mainarray has not been changed but the index array now contains theindexes of the main array in the sorted order. From this point,you can reorder the main array using the index array, which youcan then discard:Dim Idx() As Long' index arrayDim Data() As EmployeeData ' data array of UDTCompare elements of the main array during sorting:Data(Idx(i)).LastName < Data(Idx(a)).LastNameReorder elements of the index array during sorting:tmp = Idx(a)Idx(a) = Idx(i)Idx(i) = tmpCopy the data array to a reference array:Dim Ref() As EmployeeData ' reference arrayRef = DataPopulate the data array in the correct order using the referencearray and the index array:For i = LBound(Data) To UBound(Data)Data(i) = Ref(Idx(i))NextAlthough this process can be many times faster than reorderingthe main array using the sorting algorithm, it can still take a longtime. To save more time, you don’t need to reorder the main arrayat all. Instead, whenever you need to access the data in the mainarray in order, simply refer to its elements using the index array.For example:Text1.Text = Data(Idx(i)).LastNameUnfortunately, using the main array with the index array can causecomplications. Sometimes you need to reflect changes made tothe main array in the index array. This can be difficult because themain array does not contain information about the index array.Therefore, changing key data in the main array requires reindexingto keep things in sync.VB4, VB5, VB6, VBALevel: Beginning—Steele Cheffers, Perth, Western AustraliaUse the Format Function for Regional SettingsAlthough the GetLocaleInfo API can retrieve just about any regionalsetting you need, VB’s own Format function also can beuseful for quick-and-dirty answers to some settings. For example,use this code to read the regional setting for the numeric decimalsymbol and thousands separators:strDecimal = Format$(0, ".")strThousands = Mid$(Format$(1000, "0,0"), 2, 1)—John Sevarts, Heerlen, NetherlandsSEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 17


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVB4/32, VB5, VB6, VBSLevel: IntermediateCreate a Duplicate RecordsetThe Clone method doesn’t quite fit the bill when you want to createa duplicate recordset—it gives you two pointers to the samerecordset, so any updates or deletes you make in one will bereflected in the other. This isn’t always desirable.You can create a true duplicate recordset easily using ADO 2.5(or later) while avoiding a second trip to the server. Use the Streamobject as an intermediary to hold the necessary XML and pass thatXML back into a second Recordset object:Dim rs<strong>One</strong> As New ADODB.RecordsetDim rsTwo As New ADODB.RecordsetDim oTempStream As New ADODB.Stream'Assumes rs<strong>One</strong> has been populated with datars<strong>One</strong>.Save oTempStream, adPersistXMLrsTwo.Open oTempStreamThe catch: The second Recordset will be a client-side cursor.If you want to commit any changes back to your database, youmust establish a new connection, set it on the Recordset, and callUpdateBatch.VB.NETLevel: Beginning—Larry JohnsonInstantiate an Object InlineYou can instantiate a new instance of an object inline, making yourcode more compact. This example shows both versions:Imports SystemPublic Class AuthorPrivate fName As StringPrivate lName As StringPublic Sub New(ByVal fName As String, ByVal lName As _String)me.fName = fNameme.lName = lNameEnd SubPublic ReadOnly Property FullName() As StringGetReturn fName & " " & lNameEnd GetEnd PropertyEnd ClassPublic Class TestPublic Shared Sub Main()'This is the more verbose methodDim author As Author = _New Author("Jon", "Goodyear")Console.WriteLine(author.FullName)'This is the less verbose methodConsole.WriteLine( _New Author("Jon", "Goodyear").FullName)End SubEnd Class—Jonathan Goodyear, Orlando, Fla.VB3, VB4, VB5, VB6, VBA, VBSLevel: BeginningLock Windows 2000 InstantlyLocking an NT workstation has never been easy. Windows 2000has a new function, LockWorkStation, that can lock the machineinstantly with a single API call:Private Declare Function LockWorkStation Lib _"user32.dll" () As LongCall LockWorkStationIn fact, because this function requires no parameters, you canreduce the code to a single line, as well as make it callable from 16-bit code, by invoking it through rundll32:Call Shell("rundll32 user32.dll,LockWorkStation", _vbNormalFocus)The workstation locks instantly when this line is executed. Here’sthe equivalent VBS code:Dim WshSHellSet WshShell = CreateObject("WScript.Shell")WshShell.Run("rundll32 user32.dll,LockWorkStation")VB4, VB5, VB6Level: Beginning—Brian Abernathy, Marietta, Ga.Enable and Disable FramesI’ve had problems enabling and disabling frame controls in <strong>Visual</strong>Basic. If a frame is set to disabled, all the controls within the frameare disabled—but they still look enabled. So I created a simplefunction that loops through all controls, locating those on aspecific frame, and enables or disables them as requested:Private Sub EnableFrameControls( _fra As Frame, ByVal Enabled As Boolean)Dim ctl As ControlOn Error Resume NextFor Each ctl In Me.ControlsIf ctl.Container Is fra Thenctl.Enabled = EnabledEnd IfNext ctlfra.Enabled = EnabledEnd SubVB5, VB6Level: Beginning—Chris O’Connor, Wantirna South, Victoria, AustraliaSkip Object Declaration Using “With” KeywordIf you want to use an object in the middle of a routine and avoiddeclaring the object in the routine, simply use this syntax:With New .End WithThis is the equivalent of:Dim X as New With X.End WithSet X = nothing—Kevin Alons, Salix, Iowa18 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB5, VB6Level: Advanced✰✰✰✰✰ Five Star TipEnd IfExit FunctionUse Argument Arrays With CallByNameVB6 introduced a new built-in function, CallByName(), as a memberof VBA.Interaction. It lets you reference an object’s method orproperty by passing its name as an argument. The syntax is:result=CallByName(Object, ProcName, _CallType [,ParamArrayArgs])Unfortunately, this function has several restrictions: It’s accessiblefrom VB6 only; when an error is raised in an ActiveX procedurecalled with the CallByName( ) function from a client, theclient always gets “error 440” regardless of the original errornumber being raised (for details, see Microsoft Knowledge Basearticle Q194418 – PRB: CallByName Fails to Return the Correct ErrorInformation); and the type of the last argument is ParamArray, soyou can’t create a dynamic list of arguments into one statement.For example, the first and second Call statements of this codedon’t work:Dim x(1)x(0) = 1: x(1) = 2'--(1) Error (dynamic list):Call CallByName(Me, "xx", VbMethod, x)'--(2) Error:Call CallByName(Me, "xx", VbMethod, Array(1, 2))'--(3) OK:Call CallByName(Me, "xx", VbMethod, 1, 2)Function xx(x1, x2)'--do something here--End FunctionHowever, you can build your own CallByName function usingTypeLib information (TLBINF32.dll) for VB5/6 applications withoutpointed restrictions:' Required for use in VB5!Public Enum VbCallTypeVbMethod = 1VbGet = 2VbLet = 4VbSet = 8End EnumPublic Function CallByNameEx(Obj As Object, _ProcName As String, CallType As VbCallType, _Optional vArgsArray As Variant)Dim oTLI As ObjectDim ProcID As LongDim numArgs As LongDim i As LongDim v()On Error GoTo HandlerSet oTLI = CreateObject("TLI.TLIApplication")ProcID = oTLI.InvokeID(Obj, ProcName)If IsMissing(vArgsArray) ThenCallByNameEx = oTLI.InvokeHook( _Obj, ProcID, CallType)End IfIf IsArray(vArgsArray) ThennumArgs = UBound(vArgsArray)ReDim v(numArgs)For i = 0 To numArgsv(i) = vArgsArray(numArgs - i)Next iCallByNameEx = oTLI.InvokeHookArray( _Obj, ProcID, CallType, v)Handler:Debug.Print Err.Number, Err.DescriptionEnd FunctionYou must use this syntax to call the CallByNameEx( ) function:Call CallByNameEx(Me, "xx", VbMethod, x)Call CallByNameEx(Me, "xx", VbMethod, Array(1, 2))Result=CallByNameEx(Me, "xx", VbMethod, x)x is an array containing the same number of elements as the calledprocedure has parameters. The CallByNameEx( ) function returnsa real error number from a calling procedure. For VB5, you mustdefine the VbCallType Enum used by the third parameter ofCallByNameEx( ), or use ordinary integers in place of the Enum.VB3, VB4, VB5, VB6Level: Beginning—Vladimir Olifer, Staten Island, N.Y.Select Case EnhancementIn the January 2000 issue of <strong>Visual</strong> Basic Programmer’s Journal, RonSchwarz wrote a nice article on VB Masonry (“VB Masonry:Applying Mortar to the Bricks”). I have found the need to domultiple tests on dissimilar variables and objects with any failingtest causing an action. Multiple embedded If...Then...ElseIf...EndIfstatements are awful to look at and troubleshoot. I found that usingSelect Case does the trick and is easy to read. Consider testingseveral items before continuing (whether to check during entry orafter is another subject). Try this:Private Function okToPost() As Boolean' Assume it's safe to post.okToPost = TrueSelect Case False' Assume you want your tests to be True' Any tests that evaluate to False will' trigger the case code.Case (lvDist.ListItems.Count > 0)' Any items in a listview control?MsgBox "No Items Selected", _vbInformation, "Post"okToPost = FalseCase IsNumeric(fvCheckNumber)' Did the user enter a valid number?MsgBox "Invalid Check Number", _vbInformation, "Post"okToPost = FalsefvCheckNumber.SetFocusCase (fvInvoiceAmount = fvCheckAmount)' Does this balance?' More case statements can follow that' evaluate to true or falseEnd SelectEnd Function—Timothy P. Sullivan, Fort Wayne, Ind.SEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 19


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersSQL Server 6.5 and upLevel: IntermediateUse the CASE Statement in a SQL SELECT ClauseSQL Server provides a mechanism for returning differentvalues in a SELECT clause based on Boolean conditions: theCASE statement. This statement resembles <strong>Visual</strong> Basic’s SelectCase statement.The SQL CASE statement has WHEN, THEN, and ELSE clausesalong with an END terminator. The syntax is:CASE [expression]WHEN [value | Boolean expression] THEN [return value][ELSE [return value]]ENDThe [expression] is optional and contains a table column or avariable. When you specify [expression] directly after the CASE,you must populate the [value] parameter in the WHEN clause:DECLARE @TestVal intSET @TestVal = 3SELECTCASE @TestValWHEN 1 THEN 'First'WHEN 2 THEN 'Second'WHEN 3 THEN 'Third'ELSE 'Other'ENDSQL Server compares this value to the expression and when thevalues match, it returns the THEN clause’s [return value]. If noneof the WHEN clauses equates to true, SQL Server returns the[return value] in the optional ELSE clause. If the ELSE clause isomitted and no value is matched, NULL is returned.If you don’t specify [expression], you must include the [Booleanexpression] in the WHEN clause. This can contain any validBoolean expression SQL Server allows:DECLARE @TestVal intSET @TestVal = 5SELECTCASEWHEN @TestVal


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB4/32, VB5, VB6Level: IntermediateGet the Drive Serial NumberYou can get the serial number of your hard drive, floppy disk, orCD-ROM easily without any additional ActiveX component. First,start a VB project, add a standard module, and place a CommandButton control on the form:'-- Module codePrivate Declare Function GetVolumeInformation _Lib "kernel32" Alias "GetVolumeInformationA" _(ByVal lpRootPathName As String, _ByVal pVolumeNameBuffer As String, _ByVal nVolumeNameSize As Long, _lpVolumeSerialNumber As Long, _lpMaximumComponentLength As Long, _lpFileSystemFlags As Long, _ByVal lpFileSystemNameBuffer As String, _ByVal nFileSystemNameSize As Long) As LongPublic Function GetSerialNumber( _ByVal sDrive As String) As LongIf Len(sDrive) ThenIf InStr(sDrive, "\\") = 1 Then' Make sure we end in backslash for UNCIf Right$(sDrive, 1) "\" ThensDrive = sDrive & "\"End IfElse' If not UNC, take first letter as drivesDrive = Left$(sDrive, 1) & ":\"End IfElse' Else just use current drivesDrive = vbNullStringEnd If' Grab S/N -- Most params can be NULLCall GetVolumeInformation( _sDrive, vbNullString, 0, GetSerialNumber, _ByVal 0&, ByVal 0&, vbNullString, 0)End Function'-- Form codePrivate Sub Command1_Click()Dim Drive As StringDrive = InputBox("Enter drive for checking SN")MsgBox Hex$(GetSerialNumber(Drive))End SubVB3, VB4, VB5, VB6Level: Intermediate—Predrag Dervisevic, Krusevac, YugoslaviaSelect Areas Within a Graphics WindowGraphics applications sometimes require users to select a rectangularregion of a picture or drawing visually. You need to providea resizing box manipulated by the pointer at run time that onlyinteracts temporarily with the graphics displayed already (downloadthis code).By assigning vbInvert to the PictureBox DrawMode propertybefore selection dragging, you can restore the background graphicsby redrawing the same rectangle. Once the selection draggingcompletes, mRect contains the selected rectangle coordinates.You can use the same technique to select a circular region orcreate the “rubber band” effect.—James Menesez, Templeton, Calif.VB6Level: BeginningRead a Complete Text File in <strong>One</strong> PassTypically, you read and process a text file by using a loop and VB’sLine Input statement:Do While Not Eof(1)Line Input #1, myStringVar$' process the line hereLoopHowever, you might want to defer processing or keep a copy ofall the lines read for repeat processing or selective editing beforewriting them out again. You can achieve this quite easily by usingVB’s Get# and Split( ) statements to read the entire file at once andsplit it into an array containing all the lines. For example, thisfunction returns the complete contents of a file as a string:Public Function ReadFile(ByVal FileName As String) _As StringDim hFile As LongDim bBuf() As BytehFile = FreeFileOpen FileName For Binary Access Read As #hFileIf LOF(hFile) > 0 ThenReDim bBuf(1 To LOF(hFile)) As ByteGet #hFile, , bBufClose #hFileReadFile = StrConv(bBuf, vbUnicode)End IfEnd FunctionThis code snippet drops the contents into an array, using the linebreak (vbCrLf) as a delimiter:Dim sLines() As StringDim sAll As StringDim i As Long' Read the contents of some filesAll = ReadFile("c:\form1.frm")' Split into individual linessLines = Split(sAll, vbCrLf)You can then process the file as desired; for example, you cansearch for specific lines:For i = LBound(sLines) to UBound(sLines)If Instr(1, "SomeText", sLines(i), _vbTextCompare) ThensLines(i) = "SomeOtherText"End IfNext iVB4, VB5, VB6Level: Beginning—John Cullen, Pedroucos, PortugalAdd Controls to a Project QuicklyVB’s Add File dialog supports only a single selection of codemodules or OCXs, so you must painstakingly select each individualfile and control one at a time.<strong>One</strong> of my previous tech tips publicized the fact that you candrag FRM, BAS, CLS, or CTL files from Windows Explorer to theProjects window in VB and VB adds them instantly to the project.What I didn’t mention is that you can also drag OCX controls fromExplorer and drop them on the VB6 Toolbox to add OCX controlsto your project just as quickly and easily.—Darin Higgins, Fort Worth, TexasSEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 21


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVB4/32, VB5, VB6Level: IntermediateAdd Multicharacter Search Capability to ListboxesUsers often complain that listboxes don’t have the same multiplekeypress search capabilities as treeviews and other objects. Butyou can simulate this behavior by adding code to a form with atimer and a listbox whose Sorted property is set to True.For this test, Form_Load adds some data and sets the defaultinterval between keystrokes. You can type in “AL” to get to Allaninstead of the first instance of an entry with an “a” in the list. Thiscan be extremely helpful in long lists. You can also convert thiscode easily for use within a custom control:Option ExplicitPrivate Declare Function SendMessage Lib "user32" _Alias "SendMessageA" (ByVal hwnd As Long, _ByVal wMsg As Long, ByVal wParam As Long, _lParam As Any) As LongPrivate Const LB_FINDSTRING = &H18FPrivate Const LB_ERR = (-1)Private sSearchstring As StringPrivate Sub Form_Load()With List1.AddItem "Adam".AddItem "Allan".AddItem "Arty".AddItem "Aslan".AddItem "Barney".AddItem "Bob"End WithTimer1.Interval = 2000End SubPrivate Sub List1_KeyPress(KeyAscii As Integer)Dim nResult As LongTimer1.Enabled = TruesSearchstring = sSearchstring & Chr$(KeyAscii)With List1nResult = SendMessage(.hWnd, LB_FINDSTRING, _.ListIndex, ByVal sSearchstring)If nResult LB_ERR Then.ListIndex = nResultKeyAscii = 0End IfEnd WithEnd SubPrivate Sub Timer1_Timer()sSearchstring = ""Timer1.Enabled = FalseEnd SubVS.NETLevel: Intermediate—Joseph L. Scally, Stamford, Conn.Use Locals to Speed Up CodeWhen working with an object’s fields repetitively in VS.NET, youcan improve performance two-fold by storing the object as a localvariable rather than a field. In VB.NET, when you use the WithmyObject ... End With syntax, a local variable is created formyObject. In C#, you must declare the local variable and set it tothe object.—Bill McCarthy, Barongarook, Victoria, AustraliaVB4/32, VB5, VB6, SQL Server 7.0Level: AdvancedExecute a SQL Server DTS Package RemotelyYou can easily execute a SQL Server 7.0 Data TransformationServices (DTS) package from VB remotely:1. Create a DTS package. It can be an import from Excel into SQLServer.2. Set a reference to Microsoft DTS Package Object Library in anyVB project. You might need to load SQL Server on the developmentmachine.3. Use the LoadFromSQLServer method on the package object:Private Sub cmdRefreshCustomers_Click()Dim oPackage As New DTS.PackageOn Error GoTo eh'Load the package that we created previously' ("Customer_List").'Use the global variables for SQL Server name, UserID,'and Password.oPackage.LoadFromSQLServer sServername, sUid, sPwd, _DTSSQLStgFlag_Default, _"", "", "", "Customer_List", 0'Execute the PackageoPackage.ExecuteMsgBox oPackage.Description, vbInformation, _"Re-import Excel sheet."'Clean up.Set oPackage = NothingExit Subeh:MsgBox Err.Description, vbCritical, _"Error refreshing Customer List"'For more sophisticated sample VB code with DTS, go'to the SQL Server 7 CD and browse these folders:'devtools\samples\dts\dtsempl1 or 2 or 3.End SubThis is a simple, powerful way to take advantage of any DTSpackage.VB3, VB4, VB5, VB6Level: Beginning—Steve Simon, Palisades Park, N.J.Embed Quotation MarksYou use quotation marks in VB to define strings, but how do youinclude them in your output? Use whichever of these methodsworks the best for you:Dim strUseChr As StringDim strUseVar As StringDim strUseDbl As StringConst Quote As String = """"strUseChr = "Hello " & Chr$(34) & "VB" & _Chr$(34) & " World!"strUseVar = "Hello " & Quote & "VB" & _Quote & " World!"strUseDbl = "Hello ""VB"" World!"Debug.Print strUseChrDebug.Print strUseVarDebug.Print strUseDblEach one prints:Hello "VB" World!—Dave Keighan, Victoria, British Columbia22 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVBSLevel: IntermediateTake a Quick Look at a FileThe Windows Script Host (WSH) supports many useful features,including VBScript’s FileSystemObject object and the ability todrag and drop filenames. You can drag and drop a data file’s icononto the script (or a shortcut to the script) to see the first 10 linesof a file, or you can click on it to get an input box. You can specifyany range of lines if you use arguments in the input box. The codegets the requested lines, puts them in a temporary file, and opensthe temp file in Notepad. This utility can come in handy when youwant to take a quick look at the layout of lines in a large file.You can download the WSH from Microsoft’s Web site athttp://msdn.microsoft.com/scripting. Be sure to download thelatest release version if the shortcut doesn’t activate with dragand-drop.Save this code into a file with a VBS extension, create ashortcut on your desktop, then take a quick look at the files:Dim sInputLine, sMain, sDim i, iP, iEndFileNameDim fso, tf, fDim nStartPos, iLineCntDim iPopupDelayDim varAr' Edit for your system!Const TempFile = "C:\Temp\temp.txt"nStartPos = 1iLineCnt = 10iPopupDelay = 4Set objArgs = WScript.Arguments' Default first line.' Default number of lines to show.' Default Popup display, in seconds.' If drag and drop was used,' the argument will be the filename.If objArgs.Count > 0 ThensInputLine = objArgs(0)ElsesInputLine = InputBox( _"Enter full name of file:" & vbCrLf & vbCrLf _& "Arguments allowed after the file name:" & _vbCrLf & " [number of lines to" & _"show] [line to start at]" & vbCrLf & _"Use single space for argument separator.", _"Display Ten Lines of a File", "C:\")sInputLine = Trim(sInputLine)End If' Clean up as we go.Set objArgs = Nothing' If the cancel button was clicked, exit.If sInputLine = "" ThenDisplayMsg "No file name entered."WScript.quit (0)End If' Get start of extension for parsing' reference point.i = InstrRev(sInputLine, ".")' If no extension, exit gracefully.If i = 0 ThenDisplayMsg "The filename " & sInputLine & _" has no extension."WScript.quit (0)End If' first arg = iLineCnt' second arg = nStartPos (optional)If i > 0 TheniEndFileName = i - 1s = Trim(Mid(sInputLine, i))If Len(s) > 0 ThenvarAr = Split(s, " ")If UBound(varAr) > 0 Then nStartPos = _CLng(varAr(1))iLineCnt = CInt(varAr(0))s = ""End IfsInputLine = Left(sInputLine, iEndFileName)End If' Use the scripting file system object to retrieve' file lines.Set fso = WScript.CreateObject( _"Scripting.FileSystemObject")' If the file doesn't exist, exit.If Not (fso.FileExists(sInputLine)) ThenDisplayMsg "The file " & sInputLine & _" does not exit."Set fso = NothingWScript.quit (0)End IfSet tf = fso.OpenTextFile(sInputLine)' Read iLineCnt file lines starting with line' nStartPosi = 1: iP = 0Do While tf.AtEndOfStream TruesMain = tf.ReadLineIf i >= nStartPos Thens = s & sMain & vbCrLfiP = iP + 1End Ifi = i + 1If iP >= iLineCnt Then Exit DoLooptf.Close' Save file lines string to a temporary file.Set f = fso.CreateTextFile(TempFile)f.Write (s)f.Close' Use the script host shell method to open the' temporary file in editor.Set WshShell = WScript.CreateObject("WScript.Shell")WshShell.Run "notepad " & TempFileSet fso = NothingSet WshShell = NothingSub DisplayMsg(sMsg)Set WshShell = _WScript.CreateObject("Wscript.Shell")WshShell.Popup sMsg, iPopupDelay, _"Exiting Windows Script Host", _vbOKOnly + vbInformationSet WshShell = NothingEnd Sub—Steve Worley, Bainbridge Island, Wash.' Check to see If there are arguments at End of' sInputLinei = InStr(i, sInputLine, " ")SEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 23


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVB.NETLevel: IntermediateArrays With Non-Zero Lower BoundsIn VB.NET, you can use the System.Array class to create an arraywith non-zero lower bounds. To do this, use the System.Array.-CreateInstance method: Array.CreateInstance(Type, Lengths( ),LowerBounds( ) ).If the array you create has two or more dimensions, you cancast it to a normal array. This example creates an array of stringsequivalent to Dim sArray(5 To 14, 8 To 27):Dim Lengths() As Int32 = {10, 20}Dim LowerBounds() As Int32 = {5, 8}Dim myArray As Array = _Array.CreateInstance(GetType(String), _Lengths, LowerBounds)' have to declare the array with the correct' number of dimensionsDim sArray(,) As String = CType(myArray, _String(,))Dim i As Int32For i = 0 To sArray.Rank - 1Console.WriteLine _("dimension {0} , LowerBound = {1}, _UpperBound = {2}", _i, sArray.GetLowerBound(i), _sArray.GetUpperBound(i))NextNote: You cannot cast to single dimension arrays because VB.NETcreates them as vectors.VB3, VB4, VB5, VB6Level: Beginning—Bill McCarthy, Barongarook, Victoria, AustraliaCopy Filenames to a ClipboardFile hierarchies are becoming more complex and file paths longeras the capacity of hard drives increases. There are still manyoccasions, however, when you can’t browse to identify a file to beused—for example, when entering a constant in VB source. Normally,there is no alternative but to type in the path—this can beboth tiresome and error-prone.The solution: Create a new Standard EXE project, delete thedefault form (Form1), and add a module (Module1 by default).Type this code into Module1:Sub Main()Clipboard.SetText Command$End SubUnder Project | Project Properties, set the Startup Object toSub Main. Give the project a suitable name (for example, filename)and create the executable (in this case, filename.exe). Create ashortcut to the executable on your desktop. When the name of fileis required, browse using Explorer or My Computer. Drag the fileand drop it onto the filename shortcut. The full path of the file isnow on the clipboard and you can paste it as necessary.This works because dropping an object onto a program orits shortcut starts it with the name of the object in the commandline. The program merely reads the command line and puts it ontothe clipboard.—M.J. Roycroft, Caversfield, Bicester, Oxfordshire, EnglandVBSLevel: BeginningFormat Strings in Proper CaseVBScript does not support the StrConv( ) function, which is usefulto format strings in proper case. Use this algorithm to help you:Public Function StrConv( _ByVal psString, ByVal plFormat) 'As StringDim lsString 'As StringDim laString 'As StringDim liCount 'As IntegerDim lsWord 'As StringConst vbProperCase = 3lsString = psStringSelect Case plFormatCase vbProperCaselsString = LCase(lsString)laString = Split(lsString)For liCount = 0 To UBound(laString)lsWord = laString(liCount)If Len(Trim(lsWord)) > 0 ThenlsWord = UCase(Left(lsWord, 1)) & _Right(lsWord, Len(lsWord) - 1)laString(liCount) = lsWordEnd IfNext liCountlsString = Join(laString)Case ElseEnd SelectStrConv = lsStringEnd FunctionThe sample call StrConv("the pHillIes wiLL PrevaiL", 3) returns thestring 'The Phillies Will Prevail'.You can use the same name for the corresponding <strong>Visual</strong> Basicfunction to facilitate easy adoption of the native version should itever be supported in future releases of VBScript. If desired, youalso can add support for the other StrConv formatting options.VBScript doesn’t currently support the Mid statement (as opposedto the Mid function) either, or you could rewrite thisalgorithm more efficiently using that.VB3, VB4, VB5, VB6Level: Beginning—Brian Egras, PhiladelphiaAdd Nonkeyboard Characters to Your ProjectWhen creating a project or Web page, you sometimes need to usecharacters not included on your keyboard—for example, ®, £, §, ©,1/ 4, 1 / 2, 3 / 4, and so on. Sure, you can use the Chr$( ) function andcreate any single character by ASCII code, but you can accomplishthis task in a simpler way.Hold down the Alt key on a keyboard. Using the numerickeypad, not the top row numbers, Type 0, then the three-digit ASCIIcode of the character. Now release the Alt key. For example, toenter the copyright symbol into a string literal, type Alt-0169.You’ll see the designated character on the screen without anyadditional coding functionality. Easy, isn’t it? All you need to knowis the ASCII code of the character you want to use. You can look thisup in the MSDN Library under Index: ASCII character set (CharacterSet 128-255), or—easier still—fire up the Character Map appletthat comes with Windows.Be aware that although most text fonts follow mostly standardcharacter mapping, deviations are common, and all bets are off ifyou end up with a symbol font.—Alex Grinberg, Holland, Pa.24 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB4/32, VB5, VB6Level: IntermediateCreate Unconventional Windows to Dazzle UsersWhen designing a portion of an application that must grab users’attention quickly—such as your company’s splash screen—youmight want to create a nonrectangular window. This code showsyou how to create a V-shaped window based on nine points:Private Type POINTAPIx As Longy As LongEnd TypePrivate Declare Function SetWindowRgn Lib "user32" _(ByVal hWnd As Long, ByVal hRgn As Long, _ByVal bRedraw As Boolean) As LongPrivate Declare Function CreatePolygonRgn _Lib "gdi32" (ByRef lpPoint As POINTAPI, _ByVal nCount As Long, _ByVal nPolyFillMode As Long) As LongPrivate Sub Form_Load()Dim lhandle As LongDim lpPoint(0 To 8) As POINTAPIlpPoint(0).x = 0lpPoint(0).y = 0lpPoint(1).x = 20lpPoint(1).y = 150lpPoint(2).x = 60lpPoint(2).y = 150lpPoint(3).x = 80lpPoint(3).y = 0lpPoint(4).x = 52lpPoint(4).y = 0lpPoint(5).x = 46lpPoint(5).y = 120lpPoint(6).x = 40lpPoint(6).y = 120lpPoint(7).x = 32lpPoint(7).y = 0lpPoint(8).x = 0lpPoint(8).y = 0lhandle = CreatePolygonRgn(lpPoint(0), 9, 1)Call SetWindowRgn(Me.hWnd, lhandle, True)End SubVB3, VB4, VB5, VB6Level: Beginning—Andrew Holliday, PhoenixClose All Child Forms in <strong>One</strong> ShotIn MDI applications, a user might have two or three or even moreMDI child windows open at any given time. But in applicationswhere you have user log-in and log-out security, you likely want tounload all open forms when the user logs out. To accomplish this,use this small piece of code:Do Until MDIform1.ActiveForm Is NothingUnload MDIform1.ActiveFormLoopIf you need to save any values in any form by default, you caninclude a call to the appropriate Save method in the Unload eventof that form.—Unnikrishnan Thampy, Floral Park, N.Y.VB3, VB4, VB5, VB6Level: BeginningReturn Roman NumeralsThis VB procedure returns decimal numbers (integers) as Romannumerals (a string), ranging from 1 to 4999. Numbers outside thisrange return the same number as a string. The optional parameteriStyle allows two different numerical styles: standard (4 = iv, 9 = ix,and so on) when iStyle = -1, or classical (4 = iiii, 9 = viiii, and so on)when iStyle = -2.The variable x should make the function more efficient, althoughyou might not notice the time saved on a fast machine:Public Function Roman(ByVal n As Integer, _Optional iStyle As Integer = -1) As StringIf n < 1 Or n >= 5000 ThenRoman = CStr(n)Exit FunctionEnd IfIf iStyle -2 Then iStyle = -1Dim sRtn As String, i As Integer, x As IntegerDim r(1 To 13) As String, v(1 To 13) As Integerr(1) = "i": v(1) = 1r(2) = "iv": v(2) = 4r(3) = "v": v(3) = 5r(4) = "ix": v(4) = 9r(5) = "x": v(5) = 10r(6) = "xl": v(6) = 40r(7) = "l": v(7) = 50r(8) = "xc": v(8) = 90r(9) = "c": v(9) = 100r(10) = "cd": v(10) = 400r(11) = "d": v(11) = 500r(12) = "cm": v(12) = 900r(13) = "m": v(13) = 1000x = UBound(v)sRtn = ""DoFor i = x To LBound(v) Step iStyleIf v(i)


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVB5, VB6Level: IntermediateUse Hidden Enum BoundsEnumerated parameters don’t prevent you from passing unenumerateddata to the function. Let’s say you have this enumeration:Public Enum geAccessTypeReadOnly = 1WriteOnly = 2ReadWrite = 3NoAccess = 4End EnumAnd you have this function:Public Function DoSomeJob( _eType As geAccessType) As LongMsgBox eTypeEnd FunctionYou can call this function like this:'call #1DoSomeJob ReadOnlyYou can also call it like this, passing a value other than theenumerated constants:'call #2DoSomeJob 45In any case, it works.If you add two variables to the enumeration and modify yourfunction implementation, you can prevent out-of-bounds casessuch as call #2 easily:Public Enum geAccessType[_minAccessType] = 1ReadOnly = 1WriteOnly = 2ReadWrite = 3NoAccess = 4[_maxAccessType] = 4End EnumPublic Function DoSomeJob( _eType As geAccessType) As LongSelect Case eTypeCase geAccessType.[_minAccessType] To _geAccessType.[_maxAccessType]MsgBox eTypeCase Else'raise error or do something elseEnd SelectEnd FunctionBy default, [_minAccessType] or [_maxAccessType] do not appearin the constant list. If you want to see them, open the ObjectBrowser, right-click inside it, and select Show Hidden Members.—Russ Kot, Northbrook, Ill.VB4/32, VB5, VB6Level: IntermediateInternational Test for Illegal CharactersI was interested to read the tip “Test for Illegal Characters” in the10 th Edition of the “<strong>101</strong> <strong>Tech</strong> <strong>Tips</strong> for VB Developers” supplement[<strong>Visual</strong> Basic Programmer’s Journal February 2000]. The tip, however,has two significant drawbacks as published. First, it requiresa function from the SHLWAPI DLL, which requires either Win98/2000 or Win95/NT with Internet Explorer 4.0 or higher. Second, itonly works, as presented, for U.S. (7-bit) character sets, requiringthose of us who work with international character sets (such asaccented characters) to consider which characters will be legalwhere our apps run.Luckily, Windows has the solution: the IsCharAlphaNumericfunction, defined in User32.dll. This function uses the currentlydefined locale when performing comparisons, thereby allowingfull use of accented characters. This sample demonstrates howyou might use this function:Public Declare Function IsCharAlphaNumeric Lib _"user32" Alias "IsCharAlphaNumericA" ( _ByVal cChar As Byte) As LongPublic Function IsAlphaNum(ByVal sInput As String) _As BooleanDim fCheck As BooleanDim i As Integer' Assume non-alphanumericfCheck = False' If we don't have any input, drop outIf Len(sInput) Theni = 0Doi = i + 1fCheck = _CBool(IsCharAlphaNumeric( _Asc(Mid$(sInput, i, 1))))Loop While fCheck And (i < Len(sInput))End IfIsAlphaNum = fCheckEnd FunctionYou may pass any single or multiple character string to thefunction IsAlphaNum. The return value will be True if all charactersare alphanumeric and False otherwise.Windows also has several other useful functions for workingwith characters in the current locale. Note, however, that allfunctions require a byte to be passed, which you can achieve bypassing the Asc( ) value of a given character (see previous example):' Check if a given character is alphabeticPublic Declare Function IsCharAlpha Lib "user32" _Alias "IsCharAlphaA" (ByVal cChar As Byte) _As Long' Check if a given character is lowercasePublic Declare Function IsCharLower Lib "user32" _Alias "IsCharLowerA" (ByVal cChar As Byte) _As Long' Check if a given character is uppercasePublic Declare Function IsCharUpper Lib "user32" _Alias "IsCharUpperA" (ByVal cChar As Byte) _As Long—John Cullen, Pedroucos, Portugal26 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB5, VB6Level: IntermediateUse CopyFromRecordset With ODBC RecordsetsYou can create an ODBCDirect recordset for use with the ExcelRange object’s CopyFromRecordset method by using theDAO.Connection object’s OpenRecordset method:Public Function CreateDaoRecordset( _ByVal sData<strong>Source</strong> As String, _ByVal sUser As String, _ByVal sPwd As String, _ByVal sSql As String) _As DAO.RecordsetDim daoWs As WorkspaceDim daoConn As DAO.ConnectionDim sConn As StringDim dbEng As DBEngineSet dbEng = New DBEngineSet daoWs = dbEng.CreateWorkspace("", "admin", _"", dbUseODBC)sConn = "ODBC;DSN=" & sData<strong>Source</strong> & ";UID=" _& sUser & ";PWD=" & sPwdSet daoConn = daoWs.OpenConnection("", , , sConn)Set CreateDaoRecordset = _daoConn.OpenRecordset(sSql, dbOpenSnapshot)End FunctionThe CopyFromRecordset method also works with Oracle8 databases.The trick: You must use a proper ODBC driver. The MicrosoftODBC driver for Oracle, msorcl32.dll version 02.573.3513.0, doesn’tsupport the NUMBER data type in this method. The Oracle ODBCdriver, sqora32.dll version 8.0.5.0.0, treats the NUMBER(n) datatype as a dbDecimal and generates “Unspecified AutomationError” in the CopyFromRecordset method. But it accepts theNUMBER data type (without precision), interpreting it as adbDouble.CopyFromRecordset doesn’t copy column names to the Excelworksheet for further data analysis or reporting, so use this simplecode instead. It copies column names to the first row of the activeExcel worksheet oWsh and copies all data from the daoRsRecordset. The code assumes oWsh and daoRs have been declaredand initialized elsewhere:oWsh.ActivateFor iCol = 0 To daoRs.Fields.Count - 1oWsh.Cells(1, iCol + 1).Value = _daoRs.Fields(iCol).NameNextoWsh.Range("A2").CopyFromRecordset daoRsSQL Server 6.5 and upLevel: Beginning—Leonid Strakovskiy, New YorkSimple Way to Debug a Stored ProcedureDebugging stored procedures can be a headache, but here’s aneasier way to trace a stored procedure’s execution: Use the PRINTstatement. PRINT lets you output and analyze variable values,which is sometimes good enough. Note a few restrictions whenusing PRINT:1. You can use only Char or VarChar data types. You mustconvert other data types to Char or VarChar in order to “print”them out.2. The printed string can be up to 8,000 characters long; anycharacters after 8,000 are truncated. (SQL Server 6.5 up to 255characters long.)3. SQL Sever 6.5 doesn’t allow inline concatenation of variables.SQL Server 7.0 and 2000 don’t have this limitation.VB3, VB4, VB5, VB6Level: IntermediateFormat Color for HTMLThis function, which converts a color value into a string suitablefor HTML, formats an RGB color value, palette index, or systemcolor constant. You accomplish this by breaking out the individualcolor values for red, green, and blue, then recombining them in theopposite order Windows likes, so HTML renderers will provide thecorrect color. The call to OleTranslateColor ensures you’re usingan actual color reference, by dereferencing system color constantsor palette indices:Public Function HtmlHexColor(ByVal ColorValue As _Long) As StringDim r As ByteDim g As ByteDim b As Byte' convert color if neededCall OleTranslateColor( _ColorValue, 0&, ColorValue)' break out color bytesr = (ColorValue Mod &H100)g = (ColorValue \ &H100) Mod &H100b = (ColorValue \ &H10000) Mod &H10000' format the return stringHtmlHexColor = "#" & _Right$("0" & Hex$(r), 2) & _Right$("0" & Hex$(g), 2) & _Right$("0" & Hex$(b), 2)End FunctionVBSLevel: Beginning—Monte Hansen, Ripon, Calif.Use the Immediate If Function in VBScript<strong>Visual</strong> Basic includes the Immediate If function (IIf), but VBScript(VBS) does not. However, you can copy this code to VBS to allowthe IIf function to be used:'VB Function not included in VBSFunction IIf(Expression, TruePart, FalsePart)If Expression = True ThenIf IsObject(TruePart) ThenSet IIf = TruePartElseIIf = TruePartEnd IfElseIf IsObject(FalsePart) ThenSet IIf = FalsePartElseIIf = FalsePartEnd IfEnd IfEnd FunctionThe function can return both objects and basic data types. Here’sa sample function from an ASP page that calls the IIf function:' Return a True or False value for a checkboxFunction CheckBoxValue(Name)CheckBoxValue = _IIf(Request.Form(Name) = "on", True, False)End Function—Conrad Sollitt, Los Angeles—Alex Grinberg, Holland, Pa.SEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 27


<strong>101</strong> TECH TIPSFor <strong>Visual</strong> <strong>Studio</strong> DevelopersVB4/32, VB5, VB6Level: Advanced✰✰✰✰✰ Five Star TipOverride Built-In KeywordsYou can override some of the built-in VB keywords with your ownversion of the function. For instance, FileDateTime is a handy builtinfunction in VB, but it suffers from one big problem: It can’t setthe date/time of a file. By overriding the built-in function, however,you can provide this feature. With this approach, the function candetermine for itself how it is being used and perform accordingly.You can override a number of keywords and functions in thismanner:Private Declare Function SystemTimeToFileTime Lib _"kernel32" (lpSystemTime As SYSTEMTIME, _lpFileTime As FILETIME) As LongPrivate Declare Function LocalFileTimeToFileTime _Lib "kernel32" (lpLocalFileTime As FILETIME, _lpFileTime As FILETIME) As LongPrivate Declare Function CreateFile Lib "kernel32" _Alias "CreateFileA" (ByVal lpFileName As _String, ByVal dwDesiredAccess As Long, ByVal _dwShareMode As Long, lpSecurityAttributes As _Any, ByVal dwCreationDisposition As Long, _ByVal dwFlagsAndAttributes As Long, _ByVal hTemplateFile As Long) As LongPrivate Declare Function SetFileTime Lib "kernel32" _(ByVal hFile As Long, lpCreationTime As Any, _lpLastAccessTime As Any, lpLastWriteTime As _Any) As LongPrivate Declare Function CloseHandle Lib "kernel32" _(ByVal hObject As Long) As LongPrivate Type FILETIMEdwLowDateTime As LongdwHighDateTime As LongEnd TypePrivate Type SYSTEMTIMEwYear As IntegerwMonth As IntegerwDayOfWeek As IntegerwDay As IntegerwHour As IntegerwMinute As IntegerwSecond As IntegerwMilliseconds As IntegerEnd TypePrivate Const GENERIC_WRITE As Long = &H40000000Private Const FILE_SHARE_READ As Long = &H1Private Const FILE_SHARE_WRITE As Long = &H2Private Const OPEN_EXISTING As Long = 3Public Function FileDateTime(ByVal FileName As String, _Optional ByVal TimeStamp As Variant) As Date' Raises an error if one occurs just like FileDateTimeDim x As LongDim Handle As LongDim System_Time As SYSTEMTIMEDim File_Time As FILETIMEDim Local_Time As FILETIMEIf IsMissing(TimeStamp) Then'It's missing so they must want to GET the timestamp'This acts EXACTLY like the original built-in functionFileDateTime = VBA.FileDateTime(FileName)ElseIf VarType(TimeStamp) vbDate Then'You must pass in a date to be validErr.Raise 450ElseSystem_Time.wYear = Year(TimeStamp)System_Time.wMonth = Month(TimeStamp)System_Time.wDay = Day(TimeStamp)System_Time.wDayOfWeek = _Weekday(TimeStamp) - 1System_Time.wHour = Hour(TimeStamp)System_Time.wMinute = Minute(TimeStamp)System_Time.wSecond = Second(TimeStamp)System_Time.wMilliseconds = 0'Convert the system time to a file timex = SystemTimeToFileTime(System_Time, Local_Time)'Convert local file time to file time based on UTCx = LocalFileTimeToFileTime(Local_Time, File_Time)'Open the file so we can get a file handle to'the fileHandle = CreateFile(FileName, GENERIC_WRITE, _FILE_SHARE_READ Or FILE_SHARE_WRITE, _ByVal 0&, OPEN_EXISTING, 0, 0)If Handle = 0 ThenErr.Raise 53, "FileDateTime", _"Can't open the file"Else'Now change the file time and date stampx = SetFileTime(Handle, ByVal 0&, _ByVal 0&, File_Time)If x = 0 Then'Error occuredErr.Raise 1, "FileDateTime", _"Unable to set file timestamp"End IfCall CloseHandle(Handle)'Return newly set date/timeFileDateTime = VBA.FileDateTime(FileName)End IfEnd IfEnd Function—Darin Higgins, Fort Worth, TexasVB4/32, VB5, VB6, SQL Server 6.5 and up, Oracle 8i and upLevel: BeginningCompare Oracle and SQL Server DatesOracle and SQL Server databases use different date/time resolutions,which poses a problem when you compare times from the twodatabases: The times will rarely be equal. Solve this problem byallowing for a margin of error. Treat the dates and times as floatingpointnumbers and remember that each day is equal to the wholenumber 1, and there are 86,400 seconds in a day. This functionmatches times within five seconds (default) of one another:Public Function MatchTime(adoFldOracle As ADODB.Field, _adoFldSQLServer As ADODB.Field, _Optional ByVal Tolerance As Long = 5) As BooleanDim dtOracle As DateDim dtSQLServer As DateDim dblTolerance As DoubleConst <strong>One</strong>Second As Double = 1 / 86400dblTolerance = <strong>One</strong>Second * TolerancedtOracle = adoFldOracle.ValuedtSQLServer = adoFldSQLServer.ValueIf ((dtOracle > (dtSQLServer + dblTolerance)) Or _(dtOracle < (dtSQLServer - dblTolerance))) ThenMatchTime = FalseElseMatchTime = TrueEnd IfEnd Function—Andy Clark, Richmond, Va.28 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> SEPTEMBER 2001


For even more tricks and tips go towww.vbpj.com or www.vcdj.comVB3, VB4, VB5, VB6Level: BeginningVB6Level: Advanced✰✰✰✰✰ Five Star TipManage Errors With Sparse Line NumberingYou might have used line numbering to track error locations, butthis technique can be a pain (and ugly) to use for every line. Sparseline numbers help you locate code sections generating errorsthrough the intrinsic Erl (error line) property.Erl captures the most recent line number so you can pinpointerror locations with whichever precision you desire. This can helpyou determine what to do in the error handler (download this code).For example, do you need to roll back the database transaction?VB3, VB4, VB5, VB6Level: Intermediate—Bob Hiltner, SeattleReplace All Occurrences of <strong>One</strong> StringWith Another StringAll programmers—especially database programmers—require afunction that replaces all occurrences of one substring with anotherstring. For example, they need to replace the single quotesin strings passed to an Oracle database with two single quotes.Using recursion in this algorithm limits its usefulness slightlybelow that of VB6’s native Replace function, as the time requiredincreases greatly in relation to the length of the searched string:Public Function strReplace(ByVal strString As _String, ByVal strToBeReplaced As String, ByVal _strReplacement As String, Optional ByVal _intStartPosition As Integer = 1) As StringDim strNewString As StringDim intPosition As IntegerOn Error GoTo ErrorHandler' intStartPosition will be one initiallyintPosition = InStr(intStartPosition, _strString, strToBeReplaced)If intPosition = 0 Then' Nothing more to do so return final stringstrReplace = strStringElsestrNewString = Left$(strString, intPosition _- 1) & strReplacement & Mid$(strString, _intPosition + Len(strToBeReplaced))' Recursively call strReplace until there are' no more occurrences of the string to be' replaced in the string passed in. We now only want' to process the remaining unprocessed part of the' string so we pass a start position.strReplace = strReplace(strNewString, _strToBeReplaced, strReplacement, _intPosition + Len(strReplacement))End IfExit FunctionErrorHandler:' Place error handler code hereEnd Function—Patrick Tighe, Eastwall, Dublin, IrelandSerialize Data Using a PropertyBagYou can serialize your data quickly by placing it into a PropertyBagobject, then reading the PropertyBag’s Contents property. Thisproperty is really a Byte array that is a serial representation of thedata in your PropertyBag object. You can use this byte array formany purposes, including an efficient means of data transmissionover DCOM:Private Function PackData() As StringDim pbTemp As PropertyBag'Create a new PropertyBag objectSet pbTemp = New PropertyBagWith pbTemp'Add your data to the PB giving each item a'unique string keyCall .WriteProperty("FirstName", "John")Call .WriteProperty("MiddleInitial", "J")Call .WriteProperty("LastName", "Doe")'Place the serialized data into a string'variable.Let PackData = .ContentsEnd WithSet pbTemp = NothingEnd FunctionTo retrieve the serialized data, simply create a new PropertyBagobject and set the serialized string to its Contents property.Convert the string into a byte array before assigning it to theContents property:Private Sub UnPackData(sData As String)Dim pbTemp As PropertyBagDim arData() As Byte'Convert the string representation of the data to'a Byte arrayLet arData() = sData'Create a new PropertyBag objectSet pbTemp = New PropertyBagWith pbTemp'Load the PropertyBag with dataLet .Contents = arData()'Retrieve your data using the unique keyLet m_sFirstName = .ReadProperty("FirstName")Let m_sMiddleInitial = _.ReadProperty("MiddleInitial")Let m_sLastName = .ReadProperty("LastName")End WithSet pbTemp = NothingEnd SubVS.NETLevel: Beginning—Mike Kurtz, McKees Rocks, Pa.Clear a Picture Property at Design TimeTo clear the picture property of a control at design time, right-clickon the icon next to the entry in the Properties window and selectReset from the popup menu.—Bill McCarthy, Barongarook, Victoria, AustraliaSEPTEMBER 2001 Supplement to <strong>Visual</strong> <strong>Studio</strong> <strong>Magazine</strong> 29

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!