07-30-2011، 09:39 AM
Customize the WebBrowser Control
The WebBrowser control is a powerful tool useful in many Web-related projects. In effect, this control provides the browser functionality of Internet Explorer in a software component that you can incorporate in your projects. In some applications it is desirable to customize the WebBrowser control's behavior. While some customization is possible through the control's properties and events, this is sometimes not sufficient. Microsoft has made a tool available that permits certain WebBrowser customizations, specifically having to do with accelerator keys and context (right-click) menus. You can disable context menus, and can disable all or selected accelerator keys. The tool is called WBCustomizer and is available by downloading WBCustom.exe from http://support.microsoft.com/support/kb/...3/2/35.ASP and executing it to extract WBCustomizer.dll and a sample project.
To use the WBCustomizer object, you must register the DLL as follows using the Run command:
regsvr32 wbcustomizer.dll
Use the full path to the DLL file as required. In your Visual Basic project the component must be selected using the References dialog box in order to permit early binding. Then, using the object is relatively simple. First, declare a variable to reference the object and create an instance of it:
Dim CustomWB As WBCustomizer
Set CustomWB = New WBCustomizer
Then, set the object properties to specify the desired customization, and associate it with the WebBrowser control. This code assumes that the WebBrowser control is named WebBrowser1. It turns off all context menus and accelerator keys:
With CustomWB
.EnableContextMenus = False
.EnableAllAccelerators = False
Set .WebBrowser = WebBrowser1
End With
The sample Visual Basic project that comes with the DLL provides examples of disabling specific accelerator keys.
Visual Basic Programming Tips 7
Avoid using the Internet Transfer Control
In theory the Internet Transfer Control is the best thing since sliced bread. In reality I consider it a real pain in the neck. Even though the latest version is vastly improved over the earlier releases, it is still buggy. Sometimes the OpenURL method does not retrieve an entire page. Also, I find that trying to use this control in a program that also uses automation to work with Office components often leads to weird and impossible to solve bugs. If you need to retrieve HTML pages, you can do so easily by calling some functions in the WinInet library. Without going into the details, here's how. First, put the following declarations in your project (in a code module):
Public Const INTERNET_OPEN_TYPE_PRECONFIG = 0
Public Const INTERNET_OPEN_TYPE_DIRECT = 1
Public Const INTERNET_OPEN_TYPE_PROXY = 3
Public Const scUserAgent = "VB OpenUrl"
Public Const INTERNET_FLAG_RELOAD = &H80000000
Public Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" _
(ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
ByVal sProxyBypass As String, ByVal lFlags As Long) As Long
Public Declare Function InternetOpenUrl Lib "wininet.dll" Alias "InternetOpenUrlA" _
(ByVal hOpen As Long, ByVal sUrl As String, ByVal sHeaders As String, _
ByVal lLength As Long, ByVal lFlags As Long, ByVal lContext As Long) As Long
Public Declare Function InternetReadFile Lib "wininet.dll" _
(ByVal hFile As Long, ByVal sBuffer As String, ByVal lNumBytesToRead As Long, _
lNumberOfBytesRead As Long) As Integer
Public Declare Function InternetCloseHandle Lib "wininet.dll" _
(ByVal hInet As Long) As Integer
Then, here's a function that returns the entire text of the specified URL:
Private Function GetHTMLFromURL(sUrl As String) As String
Dim s As String
Dim hOpen As Long
Dim hOpenUrl As Long
Dim bDoLoop As Boolean
Dim bRet As Boolean
Dim sReadBuffer As String * 2048
Dim lNumberOfBytesRead As Long
hOpen = InternetOpen(scUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
hOpenUrl = InternetOpenUrl(hOpen, sUrl, vbNullString, 0, INTERNET_FLAG_RELOAD, 0)
bDoLoop = True
While bDoLoop
sReadBuffer = vbNullString
bRet = InternetReadFile(hOpenUrl, sReadBuffer, Len(sReadBuffer), lNumberOfBytesRead)
s = s & Left$(sReadBuffer, lNumberOfBytesRead)
If Not CBool(lNumberOfBytesRead) Then bDoLoop = False
Wend
If hOpenUrl <> 0 Then InternetCloseHandle (hOpenUrl)
If hOpen <> 0 Then InternetCloseHandle (hOpen)
GetHTMLFromUrl = s
End Function
Visual Basic Programming Tips 8
Send raw data to the printer port
While Visual Basic makes some things very easy, other things are ridiculously difficult. For example, sending raw data to the printer port seems to be impossible. When I say "raw," I mean really raw - I am not talking about unformatted text. I wanted to make the printer port's 8 data lines output a specific bit pattern, such as 00000101, under program control. My task was to use the computer to control a piece of laboratory equipment that was equipped with a parallel interface, but this technique might also be applicable for programming the many other types of parallel port devices, such as external mass storage, that are available today. I tried a variety of Windows API calls without success and finally realized that this was one of those few times when going outside of Visual Basic is unavoidable. The solution lay in Visual C++, which has the _outp() function to send a byte directly to a hardware port (like Quick Basic's old out() statement). I compiled the code into a DLL called PPORT.DLL, which you can download in ZIP format by clicking here. Put the DLL in the \Windows\System folder, then declare the function in your Visual Basic program as follows:
Declare Function SendByteToPort Lib "pport.dll" (p As Integer, b As Integer) As Integer
The argument p is the port number, which for LPT1: is &H378. The argument b is the value, in the range 0-255, to be sent. The function's return value can be ignored. Be careful when using this function, because by writing directly to the hardware it bypasses the Windows safety nets. You can get into all sorts of trouble if you are not careful! I have had reports that this does not work under Windows 2000.
Since I wrote the above programmer Kris Tilly has informed me that you can send raw data to the printer port without resorting to a DLL written in C++, but rather by calling Windows API functions from your Visual Basic program. I have not tested this, but Kris says it works fine.
Public Type DOCINFO
pDocName As String
pOutputFile As String
pDatatype As String
End Type
Public Declare Function ClosePrinter Lib "winspool.drv" (ByVal _
hPrinter As Long) As Long
Public Declare Function EndDocPrinter Lib "winspool.drv" (ByVal _
hPrinter As Long) As Long
Public Declare Function EndPagePrinter Lib "winspool.drv" (ByVal _
hPrinter As Long) As Long
Public Declare Function OpenPrinter Lib "winspool.drv" Alias _
"OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, _
ByVal pDefault As Long) As Long
Public Declare Function StartDocPrinter Lib "winspool.drv" Alias _
"StartDocPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, _
pDocInfo As DOCINFO) As Long
Public Declare Function StartPagePrinter Lib "winspool.drv" (ByVal _
hPrinter As Long) As Long
Public Declare Function WritePrinter Lib "winspool.drv" (ByVal _
hPrinter As Long, pBuf As Any, ByVal cdBuf As Long, _
pcWritten As Long) As Long
Dim lhPrinter As Long
Dim lReturn As Long
Dim lpcWritten As Long
Dim lDoc As Long
Dim sWritteRdata As String
Dim MyDocInfo As DOCINFO
lReturn = OpenPrinter(Printer.DeviceName, lhPrinter, 0)
If lReturn = 0 Then
MsgBox "The Printer Name you typed wasn't recognized."
Exit Sub
End If
MyDocInfo.pDocName = "AAAAAA"
MyDocInfo.pOutputFile = vbNullString
MyDocInfo.pDatatype = vbNullString
lDoc = StartDocPrinter(lhPrinter, 1, MyDocInfo)
Call StartPagePrinter(lhPrinter)
For I = 1 To NumCopies
sWritteRdata = ""
lReturn = WritePrinter(lhPrinter, ByVal sWritteRdata, _
Len(sWritteRdata), lpcWritten)
lReturn = EndPagePrinter(lhPrinter)
Next I
lReturn = EndDocPrinter(lhPrinter)
lReturn = ClosePrinter(lhPrinter)
Visual Basic Programming Tips 9
Reduce the size of Visual Basic distribution files.
The Visual Basic Application Setup Wizard makes it easy to create a set of distribution files for your program. Given all of the support files that a Visual Basic program requires, however, distribution files tend to be rather large. This is a special concern when creating distribution files for download. It is important to ensure that your distribution files are as compact as possible. Yet, the Setup Wizard will sometimes include files in the distribution package that you are sure - or almost sure - that your program doesn't need. Why is this?
The problem is that the Setup Wizard is not smart enough to determine which software components your program actually needs, but can only detect those components that were available within the Visual Basic development environment at the time the program was compiled. For example, if the "SooperDooperGrid Control" was checked in the Components dialog box, its files will be included in the distribution files even though your program does not use it. Therefore, you need to remove all of the components that your program does not use from the Components dialog box before compiling your program. Fortunately, Visual Basic is pretty good at preventing you from removing components that are in use. I shrank a 2.5MB download to 1.6MB using this technique, so the gains can be significant.
Visual Basic Programming Tips 10
Reversing character order in a string
The function presented here is passed a string and returns the string with its character order reversed. "ABC" becomes "CBA" etc. Note that Visual Basic 6 has its own function StrReverse that does the same thing, so this is for users of earlier versions of Visual Basic.
Public Function Reverse(s As String) As String
' Returns a string with the characters
' in s in reverse order.
Dim buf As String, i As Long
If Len(s) < 2 Then
Reverse = s
Exit Function
End If
buf = ""
For i = Len(s) To 1 Step -1
buf = buf & Mid(s, i, 1)
Next i
Reverse = buf
End Function
Visual Basic Programming Tips 11
Creating Synthetic Keystrokes
Visual Basic has a statement SendKeys that can be used to "generate" certain keystrokes just as if they had been pressed by the user. However this function is limited in that it cannot be used to simulate all possible keystroke combinations. I discovered this recently when trying to capture screens from within a Visual Basic program - you cannot send Alt+PrintScr using SendKeys. I was informed by reader Matt Hart that you can accomplish essentially the same thing with an API function, Keybd_Event which does not have the limitations of SendKeys. This function "synthesizes" a keystroke, and it does a pretty good job because this is the same function that is called by the keyboard’s hardware interrupt handler. As far as I can tell, the operating system has no way of knowing whether a keystroke came from someone's grubby little finger actually hitting the keyboard or was generated in software using Keybd_Event. Here’s the function declaration:
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal _
bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Without going into all the details, here’s how you can capture screens to the clipboard. First declare the following constant:
Const VK_SNAPSHOT As Byte = &H2C
Then capture the active Visual Basic window to the clipboard with:
Call keybd_event(VK_SNAPSHOT, 0, 0, 0)
Or capture the entire screen (hiding the Visual Basic window first, if desired) with:
Call keybd_event(VK_SNAPSHOT, 1, 0, 0)
You’ll find a sample program that uses this code to capture AVI video images on Matt’s web page at http://www.blackbeltvb.com/
Visual Basic Programming Tips 12
Validating web links
You can be pretty sure that www.microsoft.com will always be a valid link, but many other web links tend to come and go as their authors move on, get bored, or whatever. If you maintain a web page with lots of external links, it is in your interest to ensure that the links are functioning. If lots of the links on your page are dead, your visitors will not be impressed! Many web page design tools, such as FrontPage, have a "validate links" command but I have found these to be fairly useless. They tell you which links are OK and which are not, but they do not permit you to do anything about the bad links (other than manually removing them from the pages). I needed a better solution.
I keep my links database in an Excel worksheet - one column for URL, one for Title, etc. I have written an Excel Basic macro that reads through the table and writes the HTML code for each of my links pages. I decided to write a Visual Basic program that would:
Use DDE to read a URL from the links worksheet.
Attempt to connect to the URL and note whether the URL is valid or not.
Use DDE to put the label "OK" or "BAD" in the worksheet to indicate whether the URL was good or not.
Continue for all the links in the worksheet.
Then, I would re-write the Excel Basic macro so that a link will be written to the HTML file only if the URL is marked as "OK." This approach has worked very well for me. Here's the code. Create a form with a Text Box, an Internet Transfer control, four Label controls, and a control array of two Command Buttons. In the General section put the following code:
Option Explicit
Const FILENAME = "c:\documents\stamps\links.xls"
Dim XLObj As Excel.Application
Change the FILENAME constant to point to your worksheet file. Here is the code for the Form_Load and Command1_Click event procedures:
Private Sub Command1_Click(Index As Integer)
Select Case Index
Case 0 'Start
Command1(0).Enabled = False
Call CheckLinks
Command1(0).Enabled = True
Case 1 ' Quit
End
End Select
End Sub
Private Sub Form_Load()
' Create the Excel object and open the worksheet.
Set XLObj = CreateObject("Excel.Application")
XLObj.Workbooks.Open FILENAME
Inet1.Protocol = icHTTP
End Sub
The bulk of the action goes on in the CheckLinks() procedure.
Public Sub CheckLinks()
Dim row As Integer, url As String
Dim buf As String, msg As String, fnf As Integer
Dim snf As Integer, tout As Integer, ok As Integer
On Error Resume Next
' Make row equal to the Worksheet row where
' your data starts.
row = 4
tout = 0
fnf = 0
ok = 0
snf = 0
' Minimize the form.
Form1.WindowState = 1
Do
' I keep URLs in column 3 © of the worksheet.
url = XLObj.Cells(row, 3)
' If it's empty we are done.
If url = "" Then Exit Do
' Try to open the URL.
Text1.Text = Inet1.OpenURL(url)
DoEvents
' If the URL returned any text, put the
' first 50 characters in a buffer. Error
' messages will be found here.
If Len(Text1.Text) > 50 Then
buf = Left(Text1.Text, 50)
Else
buf = Text1.Text
End If
' Catch a time out error.
If Err = 35761 Then
msg = "Timed out"
tout = tout + 1
Err.Clear
' If nothing is returned it usually means
' that the server was not found.
ElseIf Text1.Text = "" Then
msg = "Server not found"
snf = snf + 1
' If error 404 is returned from the URL
' it means the server was found but
' the requested file was not present.
ElseIf InStr(1, buf, "404") Then
msg = "File not found"
fnf = fnf + 1
' Otherwise the link is OK.
Else
msg = "OK"
ok = ok + 1
End If
' Put the result in column 5 of the worksheet.
XLObj.Cells(row, 5) = msg
' Move to the next row.
row = row + 1
' Display current status on form.
Form1.Caption = ok + fnf + snf + tout
Label1.Caption = "OK: " & ok
Label2.Caption = "File not found: " & fnf
Label3.Caption = "Server not found: " & snf
Label4.Caption = "Timed out: " & tout
Loop While True
' When all links checked, restore the form.
Form1.WindowState = 0
' Close the worksheet.
XLObj.Workbooks.Close
' Delete the object.
Set XLObj = Nothing
' Display a summary of results.
buf = "OK: " & ok & vbCrLf
buf = buf & "Server not found: " & snf & vbCrLf
buf = buf + "File not found: " & fnf & vbCrLf
buf = buf & "Timed out: " & tout
MsgBox (buf)
End Sub
The program takes a while to run - a couple of hours to check a few hundred links - but because it is running in the background you can continue to use your system for other tasks. You can download a ZIP file containing the Visual Basic project by clicking here.
Note: Since originally creating this project I have made some modifications that increase its reliability and flexibility. First, the links information is kept in an Access database rather than in an Excel workbook. The Visual Basic program uses ADO to read and write the data table. Second, I have abandoned the Internet Transfer control in favor of using the WinINet library to implement the http protocol. Details of using WinINet are presented here.
Visual Basic Programming Tips 13
Easy Creation of Database Connection Strings
For a Visual Basic program to connect to a data source, a connection string is often required. This string specifies the data source, user name, security information, and other aspects of the data connection. Here's an example of a fairly simple connection string:
Provider=Microsoft.Jet.OLEDB.3.51;Persist Security Info=False;User ID=AliceK;Data Source=C:\data\Northwind.mdb
Who can remember all the details required to write connections strings? I know I can't! Visual Basic can help you, as follows:
Make sure the ADO Data Control is displayed in the Visual Basic toolbox. This is not the standard Data control, but is identified by a tooltip that says "ADODC". If it is not present you will have to add it by pressing Ctrl+T to open the Components dialog box then putting a checkmark next to "Microsoft ADO Data Control 6.0."
Use the usual techniques to place an ADO Data Control on a form.
In the Properties window, select the ConnectionString property then click the ellipses button (...) in the right-hand column.
The control's property page displays, Select the Use Connection String option, then click the Build button.
The next dialog box contains several tabs on which you can enter and select the options for the database connection, including the provider, the DSN (if you are using one), the user name and password, and the read/write permissions. You can also test the connection from this dialog box.
When you are finished defining the connection, click OK. You will return to the control's Property Page. The generated connection string will be inserted in the relevant text box.
Copy the generated connection string from the Property Page to your code.
Delete the ADO Data Control from the form.
This method for creating connection strings is a lot faster and results in fewer errors than doing it manually.
Visual Basic Programming Tips 14
Aligning Controls at a Specific Position
The Visual Basic form designer makes it easy to align a group of controls with one member of the group. Simply select the controls by holding down shift while clicking each control, being sure to click the target control (the one the others will be aligned to) last. Then, select Format|Align, then select Lefts for vertical alignment or Tops for horizontal alignment.
But what if you want to align controls at a specific position rather than to an existing control? For example, you might want to align several TextBox controls so their left edges are all exactly 50 twips from the left edge of the form. Here's how:
Select all the controls as described above. It does not matter which control is selected last.
In the Properties window, enter the desired distance from the left edge of the form in the Left property. Or, to align at a fixed distance from the top of the form, enter the distance in the Top property.
This technique can be extended to other design tasks. When more than one control is selected, the Properties window displays only those properties that are common to all the controls. Changing a property value is automatically applied to all the selected controls.
Visual Basic Programming Tips 15
Don't Forget the Tag Property
Most Visual Basic programmers never make use of the Tag property, available with most controls. After all, this property doesn't do anything so why bother with it? The truth is, it can be very useful in a variety of situations. You can store any information you want in a control's Tag property, and use that information any way you want.
One example is when you create a control array of CommandButton controls. Programmers usually use the Index argument that is passed to the Click event procedure to identify which button in the array was clicked. By assigning each button a descriptive Tag property you can use that instead and have more readable code. Here's the code for a simple example:
Private Sub Command1_Click(Index As Integer)
Select Case Command1(Index).Tag
Case "Exit"
' Code to exit the program goes here.
Case "Cancel"
' Code to cancel goes here.
End Select
End Sub
Another way to use the Tag property is when working with a TreeView control. A TreeView is made up of nodes, and it can be useful to have some data associated with each node in addition to the text it displays. This is easily accomplished by using the Tag property of each Node object.
Visual Basic Programming Tips 16
Do You DoEvents?
Lots of programmers don't even know about Visual Basic's DoEvents function. This is not surprising because few Visual Basic programs need it. DoEvents returns control to the operating system temporarily, allowing it to process other events that may have occurred. In my experience, the only time DoEvents is needed is when a program has code that takes a long time to execute, such as certain complex mathematical calculations. By calling DoEvents at strategic locations in your code you can improve program responsiveness.
To see what I mean, create a Standard EXE project in Visual Basic and place one CommandButton and one TextBox on the form. Then, put the following code in the Command Button's Click event procedure:
Private Sub Command1_Click()
Dim i As Long, j As Long
For i = 1 To 100
Text1.Text = i
For j = 1 To 100000
Next
Next
Text1.Text = "Done"
End Sub
You can see that the code has one loop within another, loops that will take a few seconds to complete (You may want to adjust the value that the inner loop counts to depending on the speed of your system). Each time the outer loop iterates, the current value of i is displayed in the text box. When the loops are finished, "done" is displayed.
What actually happens when you run the program, however, is that the text box does not change until "done" is displayed. The problem is that the system was so busy executing the loops that the requests to display i in the text box got stalled in Windows queue. When the loops were finished, all these requests were processed, too quickly for you to see on the screen.
Now, place a call to DoEvents in the code, just after the Text1.Text = i statement. When you run the program you will see that the text box "counts up" the values of i, just as you would expect. Calling DoEvents frees up the system to process the request, then returns control to the Visual Basic program.
DoEvents is not without potential problems. For example, if you call DoEvents from a procedure you must be sure that the same procedure cannot be called again before execution returns from the first call - otherwise unpredictable results may occur. Likewise, DoEvents should be avoided if other applications could interact with the procedure in unforeseen ways. Use of a Timer control or isolation of long-running code in an ActiveX component are two other approaches to improving program responsiveness.
Visual Basic Programming Tips 17
Screen Capture in Visual Basic
Capturing images of the screen is useful in many situations, such as when documenting computer glitches or writing technical manuals and software documentation. Can you capture a screen image from a Visual Basic program? You bet. There's a complex way, involving Windows device contexts and the API, but it would be easier to use Window's built-in screen capture capability. Pressing the PrintScrn button copies the entire screen to the clipboard, while Alt+PrintScrn copies just the active window. All a Visual Basic program would need to do is to "press" this key.
Unfortunately, you cannot use Visual Basic's SendKeys for this purpose - it just won't work with PrintScrn. You must use the Keydb_Event API function. This function synthesizes a keystroke, and because it is the same function that is called by the keyboard’s hardware interrupt handler, it should do a good job of fooling an application - or Windows in this case - into thinking that the key was actually pressed. The function declaration is as follows:
Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, _
ByVal bScan As Byte, ByVal dwFlags As Long, _
ByVal dwExtraInfo As Long)
I can't go into all the details of this statement, but here’s how to capture screens to the clipboard. Start by declaring the following constant:
Const VK_SNAPSHOT As Byte = &H2C
Then you capture the active window to the clipboard with this code:
Call keybd_event(VK_SNAPSHOT, 0, 0, 0)
To capture the entire screen you use the following:
Call keybd_event(VK_SNAPSHOT, 1, 0, 0)
Once the screen image is on the clipboard, you will use the Clipboard object's GetData method to retrieve it as required by your application. The following code captures the screen and displays it in a PictureBox control, from where it can be saved to disk is desired.
Clipboard.Clear
Call keybd_event(VK_SNAPSHOT, 0, 0, 0)
DoEvents
Picture1.Picture = Clipboard.GetData(vbCFBitmap)
Please note two things about this code. First, you should call Clipboard.Clear before "pressing" PrintScrn to remove any old data from the clipboard. Second, you should call the Visual Basic DoEvents function before trying to retrieve the captured image, to allow Windows the time to process the keybd_event call.
Visual Basic Programming Tips 18
Determining Drive Type
A disk drive can be one of several types. Sometimes a program needs to be aware of the type of the drive in order to know what it can and cannot do. For example, a program cannot write to a CD-ROM drive. Likewise, if a drive a removable a program cannot assume anything about what files are on the drive. You can use an API function, GetDriveType, to easily determine the type of a drive. This function has the following declaration, which must be included in your project:
Declare Function GetDriveType Lib "kernel32" Alias _
"GetDriveTypeA" (ByVal nDrive As String) As Long
The function's argument is s string identifying the drive's root folder - for example, "c:\" or "f:\". The return value is a type Long, one of the following depending on the drive type. This list includes the constants that are usually defined for use in the program.
DRIVE_REMOVABLE = 2
DRIVE_FIXED = 3
DRIVE_REMOTE = 4
DRIVE_CDROM = 5
DRIVE_RAMDISK = 6
If the function returns any other value it means the drive was not recognized (that is, does not exist or is not available, as in a network drive).
The following Visual Basic procedure illustrates the use of this API function. The argument to this procedure must be in the form required by the GetDriveType function, that is the drive letter followed by ":\". Of course the API function declaration and the constant definitions must be included in any program that uses this procedure.
Private Sub DriveType(dr as string)
Dim dr As String
Dim drivetype as Long
drivetype = GetDriveType(dr)
Select Case drivetype
Case DRIVE_REMOVABLE:
MsgBox "Drive " & dr & " is a removable drive"
Case DRIVE_FIXED:
MsgBox "Drive " & dr & " is a fixed (hard) drive"
Case DRIVE_REMOTE:
MsgBox "Drive " & dr & " is a remote (network) drive"
Case DRIVE_CDROM:
MsgBox "Drive " & dr & " is a CD-ROM drive"
Case DRIVE_RAMDISK:
MsgBox "Drive " & dr & " is a RAM drive"
Case Else:
MsgBox "Drive " & dr & " was not recognized"
End Select
End Sub
Visual Basic Programming Tips 19
Filtering TextBox Input
The TextBox control is commonly used to accept keyboard input from the user. In some situations you may want to restrict what the user can type to ensure that invalid data is not entered. For example:
- A TextBox for ZIP code could be restricted to accepting digits (and, if ZIP+4 is being used, the hyphen).
- A TextBox for entry of a person's first name could be restricted to letters.
- A TextBox for an email address could be restricted to letters, digits, and the period and @ characters.
To restrict entry into a TextBox you use its KeyPress event procedure:
Private Sub Text1_KeyPress(KeyAscii As Integer)
End Sub
The KeyAscii argument is a code identifying the key that was pressed as an ASCII value. Code in the KeyPress event procedure can examine the value of KeyAscii to see if a permitted character was entered. If so, do nothing and the character will be passed through to the TextBox. If the key is not permitted, set KeyAscii to 0 and the keypress is cancelled. KeyPress should always pass through the BackSpace key (ASCII value = 8) to permit the user to delete erroneous characters.
The following KeyPress event procedure lets only the digits 0-9 through, plus Backspace. The ASCII values for the characters 1 though 9 are 48-57.
Private Sub Text1_KeyPress(KeyAscii As Integer)
If KeyAscii = 8 Then Exit Sub
If KeyAscii < 48 Or KeyAscii > 57 Then
Beep
KeyAscii = 0
End If
End Sub
Note that the KeyPress event procedure will not intercept editing keys such as Del or the arrow keys, so you do not need to make provision to pass them through in your code.
Visual Basic Programming Tips VB6 20
Counting Strings
Visual Basic makes it easy to find one string within another using the InStr function. But what about counting the number of times one string occurs within another? One use for this is when performing Web searches. The more times a term of interest appears on a Web page, the more likely the page is to be relevant.
It's not too difficult to program string counting yourself. You can use the InStr function, calling it repeatedly until it returns 0. With each call after the first one, update the Start position to the location of the last "hit" plus one so that InStr will find the next occurrence of the target. Keep track of the hit count, and when InStr returns 0 you are done.
The code shown here shows how. The function is named CountSubstrings. You pass it the target string to be searched, the template you are looking for, and a binary value indicating whether you want text searches to be case-sensitive. Note that the code takes care of three "error" conditions that might occur:
- The target string is blank.
- The template string is blank.
- The template is longer than the target.
In any of these situations the function returns -1.
Public Function CountSubstrings(target As String, _
template As String, CaseSensitive As Boolean) _
As Integer
' Returns the number of times template occurs in target.
' Returns -1 if either string is blank or if template
' is longer than target. If CaseSensitive is true, performs
' a case-sensitive comparison for text. If false, the
' comparison is case-insensitive.
Dim pos1 As Integer, pos2 As Integer, count As Integer
If Len(target) = 0 Or Len(template) = 0 Or _
Len(template) > Len(target) Then
CountSubstrings = -1
Exit Function
End If
count = 0
pos2 = 1
Do
If CaseSensitive Then
pos1 = InStr(pos2, target, template, vbBinaryCompare)
Else
pos1 = InStr(pos2, target, template, vbTextCompare)
End If
If pos1 > 0 Then
count = count + 1
pos2 = pos1 + 1
End If
Loop Until pos1 = 0
CountSubstrings = count
End Function
Visual Basic Programming Tips 21
Create a Temporary File Name
The only requirement for a temporary file name is that the file name not already be in use in the folder where the file will be created. The function presented here takes a path as its one argument - the path where you nee the temporary file - and returns a filename (with path) that is not present in the specified folder and can therefore be safely used for a temporary file. If a blank string is passed as the path argument, the application path (App.Path) is used. The function returns a blank string on error. The function does not verify that the path that is passed to it is valid.
The operation of the code is simple. It starts with the value 1 and makes a file name from it (1.tmp). It then checks to see if this file already exists in the specified folder using the Dir function. If not, the name is suitable and is returned by the function. If the file exists, the value is increased and the new file name (2.tmp, 3.tmp, etc.) is tried until an unused name is found.
Public Function MakeTempFileName(path As String)
' Returns a filename (with path) that is not
' already in use in the indicated path. Name
' has the form path\1.tmp, path\2.tmp, etc.
' If path is blank then App.Path is used.
' Does not verify that path is valid.
Dim x As Integer, s As String
If path = "" Then path = App.path
' Be sure path ends with \.
If (Right(path, 1) <> "\") Then path = path & "\"
x = 0
Do
x = x + 1
s = path & x & ".tmp"
Loop Until Dir(s) = ""
MakeTempFileName = path & x & ".tmp"
End Function
Visual Basic Programming Tips 22
Getting and Using Screen Information
A Visual Basic program needs to be able to run on different systems. In particular, the video equipment is likely to differ from one system to another. Joe may have an older laptop with 800x600 resolution, while Alice is running a new 21 inch monitor at 1600x1200 pixels. For some programs these video differences may not be important, but in other cases you may want to make sure that your program makes the best possible use of the screen real estate and resolution. You can use the Screen object to obtain information about the display hardware.
Recall that Visual Basic uses twips for screen measurements, with 1 twip supposedly equal to 1/1440 inch. You can determine the screen's size in twips from two of the Screen object's properties, Screen.Width and Screen.Height. In theory you could use this information to determine the actual screen size as follows:
ScreenWidthInInches = Screen.Width/1440
ScreenHeightInInches = Screen.Height/1440
I have found, however, that the actual size of a twip varies a lot from system to system. On mine, for example, a twip is a lot closer to 1/1000 inch, and the above calculations will report my 21 inch monitor as being about. 9x12 inches! The twip values are accurate in terms of positioning items on the screen, though, and you can use them to position forms on the screen without going off the edge. This code, for example, centers a form on the screen:
Form1.Move (Screen.Width - Form1.Width) / 2, _
(Screen.Height - Form1.Height) / 2
Here's another example that positions the form at the lower right corner of the screen on any system (although some of the form may be covered by the Windows task bar):
Form1.Move Screen.Width - Form1.Width, Screen.Height - Form1.Height
Visual Basic Programming Tips 23
Maximizing and Evaluating Computational Performance
With today's multi-gigahertz computers, programmers are rarely concerned with the speed of computations. Code runs plenty fast, and the bottlenecks for program performance are much more likely to be related to network access and other factors that are beyond the programmer's control. At times, however, raw computation speed does become an issue. This is most likely to happen when you are performing complex recursive tasks such as certain mathematical calculations and text processing operations. In these situations, even minor improvements to your code speed are likely to have an impact on the program's usability. Shortening a computation from, say, 60 seconds to 40 seconds is quite noticeable to the user. Here are some coding tips to maximize the performance of long computations:
Do not use type Variant variables unless it is unavoidable.
For numeric variables, use the smallest type possible for the data. That is, for integer data use Byte in place of Integer, and use Integer in place of Long. For floating point data use Single in place of Double.
For string data, use fixed-length strings as opposed to variable length strings whenever possible.
Do not write to the screen during long computations.
Minimize procedure calls. If a section of code is executed many times, it is better to place it in-line than to place it in a separate procedure that must be called with the attendant processing overhead.
While writing your program you can use the Timer function to test the speed of calculations and help you in fine-tuning the code for maximum performance. The Timer function returns the number of seconds since midnight, accurate to 1/100 of a second. Set one variable equal to Timer just before calculations start, and set another variable equal to Timer when they are complete. A simple subtraction will tell you how long the computations took. Be sure to run your tests on a compiler version of the program and not in the IDE.
Visual Basic Programming Tips 24
Create Blinking Text on a Form
Blinking text is great way to call attention to something on the screen. It's easy to implement blinking text using Visual Basic's Timer control. This example shows you how to make a Label control blink, but the same approach could be used for other controls as well.
When you place a Timer control on a form, it does not display when the program runs - it's available "behind the scenes." The Timer control has two important properties. The Interval property determines how often the Timer "ticks." The value is in milliseconds, so a setting of 500 will give you a twice per second Timer which I think is a good rate for blinking text. The Enabled property determines whether the Timer is running (Enabled = True) or stopped (Enabled = False).
The actual blinking is done in the Timer event procedure, which fires each time the Timer "ticks." One approach is to toggle the Label control's ForeColor property between black, which makes it visible, and the value of its BackColor property, which makes the text invisible. You must check the current value of the ForeColor property and then set it accordingly, as shown here:
Private Sub Timer1_Timer()
If Label1.ForeColor = Label1.BackColor Then
Label1.ForeColor = vbBlack
Else
Label1.ForeColor = Label1.BackColor
End If
End Sub
You could also implement a different type of blinking, for example having the text alternate between red and green:
Private Sub Timer1_Timer()
If Label1.ForeColor = vbRed Then
Label1.ForeColor = vbGreen
Else
Label1.ForeColor = vbRed
End If
End Sub
To turn the blinking on or off, set the Timer control's Enabled property to True or False. When you turn the blinking off, you want to make sure that the text is not left in the invisible state, or with an inappropriate color. To do so, include code to set the ForeColor property to the desired color at the same time you turn the Timer control off:
Timer1.Enabled = False
Label1.ForeColor = vbBlack
Visual Basic Programming Tips 25
Using Command Line Arguments
Command line arguments date from the days of DOS when programs were run by entering the program name at the command line. Many programs could accept arguments placed after the program name, as shown here:
C>ProgramName argument1 argument2
These command line arguments could be read by the program and used according to the program's design. For example, some programs accepted an argument specifying the data file to open, and others accepted arguments that set certain program options. With the Windows operating system, where programs are started with a click of the mouse, the command line may seem to be a relic of the past, but it's not. Your Visual Basic programs can still use command line argument.
But Windows has no command line, so how can you pass command line arguments to a program? There are two ways. In the Visual Basic environment, when writing and testing your program, you display the Project Properties dialog box (Select Project|Properties) and, on the Make tab, you enter the arguments in the Command Line Arguments field. After the program is compiled you must use a shortcut to use command line arguments:
Create a shortcut for the compiled Visual Basic program.
Right-click the shortcut and select Properties.
In the Properties dialog box, select the Shortcut tab.
The Target field will contain the command used to start the program. This command will include the path to the compiled program plus the program name, for example "c:\program files\myprogram.exe".
Edit this text to include the command line argument(s) after the program name. Be sure to use a space between the name and the arguments, and put the arguments inside the quotes (if present). For example "c:\program files\myprogram.exe argument1 argument2".
Click OK to close the Properties dialog box.
Now, whenever this shortcut is used to start the program, the specified arguments will be passed to it. By creating more than one shortcut to the same program, you can start the program with different arguments depending on the situation.
In a Visual Basic program, the Command$ function returns a string containing any command line arguments that were passed when the program was started. The arguments (if more than one) are not parsed in any way - they are returned together in a single string. This string is blank if no arguments were passed. It is totally up to your program what it does, if anything, with the arguments it receives. Command line arguments are not for every programming situation, but they can provide some additional flexibility in some circumstances.
Visual Basic Programming Tips 26
Use Environment Variables
Environment Variables are maintained by the Windows operating system. Each variable consists of a name and a value. You can see the current environment variables by opening a Command Prompt window and typing Set. These variables are system-wide and can be used by any program. Many of them have to with the operating system, but you can create environment variables for use by your Visual Basic programs. Because such variables are available to all programs, they provide a convenient approach when you need to set options or operating parameters for multiple programs.
There are two ways to create or change an environment variable. In a batch file, such as AutoExec.bat, you use the Set command as follows:
Set varname=value
You can also open System from the Windows Control Panel. On the Advanced tab, click Environment Variables and use the dialog box tools to add, edit, or delete variables. Note that there are User variables, which are set for and can be changed by the currently logged on user, and System variables, which can be added/changed only by someone with administrator privileges (Windows NT/2000/XP).
To access environment variables in your Visual Basic program, you use the Environ function. There are two ways to use this function. If you pass the name of an environment variable the function returns the value of that variable, or a blank string if the variable is not defined. If you pass a number, the function returns the entire environment string (the variable name, equal sign, and value) of the variable at the specified position. If there is no variable at the specified position, the function returns a blank string. This code displays all environment variables in a Message Box:
Dim buf As String, msg As String, idx As Integer
idx = 1
Do
buf = Environ(idx)
msg = msg & buf & vbCrLf
idx = idx + 1
Loop Until buf = ""
MsgBox msg
By using the Environ function, your Visual Basic program can retrieve the values of any relevant environment variables and use this information as needed.
Visual Basic Programming Tips 27
Prevent Multiple Program Instances From Running
Many Windows programs, including programs created with Visual Basic, permit more than one instance, or copy, of the program to run at the same time. You can, for example, have two or more copies of Notepad running at the same time, editing different text files. There are situations in which permitting multiple instances of a program is not a good idea. A program might monitor a specific folder and keep track of new files that are placed there; there is never a need to have two instances of such a program running. Likewise, a program that is a front-end to a database should be restricted to one instance because having two or more copies running at the same time could lead to data errors.
To prevent multiple copies of a program from running, use the App.PrevInstance property. This property returns True if there is another instance of the program running. You can check this property in a program's Load event procedure. If it returns True, you can display a message to the user and, if desired, activate the running copy before terminating the new, duplicate instance. Here's a code example that you would place in the Load event procedure:
If App.PrevInstance Then
MsgBox "Another instance of this program is already open."
AppActivate App.Title
Unload Me
End If
Visual Basic Programming Tips 28
Avoid the End Statement
Visual Basic's End statement immediately terminates the program, and because it's so easy to remember a lot of programmers use it regularly for program termination. There's a problem with End, however, in that the program's forms are not unloaded properly. Specifically, each form's Unload event procedure is not triggered. If your program uses the Unload event procedure to save data, close files, and perform other cleanup tasks, then using End to terminate the program is clearly not a good idea.
The preferred way to terminate a program is to unload all of its forms. With a single form program this is not a problem - just execute
Unload Me
When a program has multiple forms, this can be more of a problem - particularly when you do not know which forms are open at the time the program is terminating. To address this task, you can use the Forms collection, which contains all of the program's currently loaded forms. The technique is to loop through the collection, unloading each form in turn until all have been unloaded. The result is that the program is terminated properly, with each form's Unload event rpocedure being triggered. Here's the code:
Public Sub TerminateProgram()
Static Unloading As Boolean
Dim idx As Integer
If Unloading Then Exit Sub
Unloading = True
For idx = Forms.Count - 1 to 0 Step -1
Unload Forms(idx)
Next idx
Unloading = False
End Sub
Note two things about this code. First, the forms are unloaded in reverse order, starting with the last one in the Forms collection. If you started with the first form in the collection, the form numbers would be reassigned by the Forms collection to fill in for the removed form, and the required code would be more complex. Second, a flag is set to prevent the unloading loop from executing if it is already in progress (if, for example, the TerminateProgram procedure was inadvertently called twice).
Remember that a form can block its own unloading by setting the Cancel argument to a non-zero value in the QueryUnload event procedure. In this case the procedure shown above may not be able to close all forms and terminate the program, and the user may have to close one or more forms manually.
Visual Basic Programming Tips 29
Clear all Text Boxes on a Form
You have probably see Web pages that contain many text fields, such as a form for online ordering. Such pages often have a Clear or Reset button that erases the text in all the fields so you can start over. You can implement a similar feature for Visual Basic forms, clearing all the Text Box controls on the form for the entry of new data.
To do so you will use the form's Controls collection. This collection contains an entry for every control on the form. By looping through the collection you can determine the type of each control using the TypeOf keyword. If a control is a Text Box, erase its text; if a control is not a Text Box, ignore it. Here's a procedure to clear all text Box controls on the form:
Public Sub ClearTextBoxes()
Dim c As Control
For Each c in Controls
If TypeOf c Is TextBox Then
c.Text = ""
End If
Next
End Sub
You can extend this technique to other controls. For example, this code clears (unchecks) all CheckBox controls on the form:
For Each c In Controls
If TypeOf c Is CheckBox Then
c.Value = False
End If
Next
Visual Basic Programming Tips 30
Implementing a Stack
A stack stores data in a last-in, first-out manner, and can be very useful in many areas of programming. This tip shows you how to implement a stack in Visual Basic. Traditionally a stack has two operations associated with it: Push to put something on the stack and Pop to retrieve something from the stack. Push always puts a data item on the top of the stack, and Pop always retrieves and removes whatever it on the top of the stack. Suppose you did the following Push operations in this order:
Push A
Push B
Push C
If you then did three Pops, you would retrieve C with the first Pop, B with the second, and A with the first. Note that a stack needs some way to signal when it is empty.
A stack is best implemented as a Class module in Visual Basic. Basically, a stack is an array that holds the data plus a pointer that indicates which position in the array is currently the "top" of the stack. In my Stack class I used a dynamic array that can expand as needed to hold new items that are pushed onto the stack. When items are removed by popping them from the stack, the array does not shrink in size to avoid the overhead of repeatedly calling ReDim.
The code for the Stack class is shown here. This implementation uses type Variant to enable the stack to hold both string and numeric data. You could also create stacks designed specifically for certain data types. Note that the Class_Initialize event procedure initializes the pointer variable to 0 and the data array to a size of 1. Also note that Pop returns Null when the stack is empty, and the program that is using a stack needs to check for this to verify that Pop has returned valid data.
Private data() As Variant
Private pointer As Long
Private Sub Class_Initialize()
pointer = 0
ReDim data(1)
End Sub
Public Function Pop()
If pointer > 0 Then
Pop = data(pointer)
pointer = pointer - 1
Else
Pop = Null
End If
End Function
Public Sub Push(value)
pointer = pointer + 1
If UBound(data) < pointer Then
ReDim Preserve data(pointer)
End If
data(pointer) = value
End Sub
To use the Stack class, create a instance of it using the usual Visual Basic syntax and then call Push and Pop as needed. The following code is from a demo program that you can use to verify how the Stack class operates. To run it, follow these steps:
1. Create a new Visual Basic Standard EXE project.
2. Place two Command Button controls on the form.
3. Create a new Class module and change its Name property to Stack.
4. Add the code listed above to this class module.
5. Add the code below to the form module.
Dim st As Stack
Private Sub Form_Load()
Set st = New Stack
End Sub
Private Sub Command1_Click()
Dim t As Single
t = Timer
Debug.Print "Pushed ", t
st.Push (t)
End Sub
Private Sub Command2_Click()
Debug.Print "Popped ", st.Pop
End Sub
When you run the program, you can click the command buttons to push and pop values (provided by the Timer function) on and off the stack. In a real-world program you'll find numerous uses for the Stack class, whenever you need to temporarily store data on a last-in, first-out basis.
Visual Basic Programming Tips 31
Converting Numbers Between Decimal and Binary
While computers may use binary numbers internally, programmers usually use decimal and sometimes hexadecimal notation in their programs. Even so, it can still be useful to see the binary representation of a number, particularly when you are working with logical operators or doing bitwise comparisons. Converting a binary representation to decimal reverses the process. This tip shows you how to convert a positive integer into a string containing its binary representation, and then back again.
Binary notation is based on powers of 2, and this fact makes the conversion process surprisingly simple. It works as follows (for decimal value X):
1. Calculate X Mod 2. The result will be either 0 or 1. This is the first (right-most) binary digit.
2. Divide X by 2, discarding any remainder (in other words, perform an integer division using the \ operator).
3. If X is 0 you are done. Otherwise return to step 1 to determine the next binary digit.
Let's work this through for the decimal value 13:
1. 13 Mod 2 is 1 so the first binary digit is 1.
2. 13 \ 2 is 6.
3. 6 Mod 2 is 0 so the second binary digit is 0.
4. 6 \ 2 is 3.
5. 3 Mod 2 is 1 so the third binary digit is 1.
6. 3 \ 2 is 1.
7. 1 Mod 2 is 1 so the fourth binary digit is 1.
8. 1 \ 2 is 0 so you are done. The result is 1101, the binary representation of 13
The code is presented here as a Visual Basic function. In addition to the value to be converted, the function takes an argument specifying the minimum number of digits in the binary value. If the actual value has fewer digits that this value, leading zeros are added to make up the difference.
Public Function DecimalToBinary(DecimalValue As Long, _
MinimumDigits As Integer) As String
' Returns a string containing the binary
' representation of a positive integer.
Dim result As String
Dim ExtraDigitsNeeded As Integer
' Make sure value is not negative.
DecimalValue = Abs(DecimalValue)
' Construct the binary value.
Do
result = CStr(DecimalValue Mod 2) & result
DecimalValue = DecimalValue \ 2
Loop While DecimalValue > 0
' Add leading zeros if needed.
ExtraDigitsNeeded = MinimumDigits - Len(result)
If ExtraDigitsNeeded > 0 Then
result = String(ExtraDigitsNeeded, "0") & result
End If
DecimalToBinary = result
End Function
Converting a binary representation to a decimal number is the reverse of the process in the above function. Each digit in a binary value represents a power of 2, starting with 2^0 for the first (right-most) binary digit, 2^1 for the second digit, and so on. Note that any number to the 0 power is by definition 1, and any number to the 1 power is the number itself. Using 1101 for another example, you have:
The first digit is 1, and 1 times 2^0 is 1.
The second digit is 0, and 0 times 2^1 is 0.
The third digit is 1, and 1 times 2^2 is 4.
The fourth digit is 1, and 1 times 2^3 is 8.
8 + 4 + 1 equals 13.
A function to perform the binary to decimal conversion is shown here.
Public Function BinaryToDecimal(BinaryValue As String) As Long
' Returns the decimal equivalent of a binary number.
Dim idx As Integer
Dim tmp As String
Dim result As Long
Dim digits As Integer
digits = Len(BinaryValue)
For idx = digits To 1 Step -1
tmp = Mid(BinaryValue, idx, 1)
If tmp = "1" Then result = result + 2 ^ (digits - idx)
Next
BinaryToDecimal = result
End Function
This function treats any character other than "1" in the binary value as a "0". You might want to add error-schecking code to ensure that the binary value contains only "0" and "1" characters.
Visual Basic Programming Tips 31
AutoRedraw and the Paint Event
Understanding the AutoRedraw property and the Paint event is important when you are using graphics in your Visual Basic programs. They are related to Visual Basic's graphics methods that let you create output on a form or a Picture Box control. These methods are Circle, Line, PaintPicture, Print, and PSet. These methods may not always work the way you expect. To see what I mean, create a new Standard EXE project and put the following line of code in the Click event procedure for the form:
Me.Circle (1500, 1500), 600
When you run the program and click the form, the circle appears just as you would expect. But try minimizing and then restoring the form - no circle (unless you click the form again)! You get the same result if you switch to another program that covers the form. What's going on - why is the circle temporary?
The answer lies in something called persistence. The form itself and any controls on it are persistent, which means that Visual Basic keeps a copy in memory and uses this copy to redraw the form on the screen when it has been hidden and then redisplayed. In contrast, the output of the graphics methods are not persistent - they are drawn to the screen only and are not saved in memory and cannot be redrawn when the form is hidden then redisplayed.
There are two ways to make the output of graphics methods persistent. One is to set the form's AutoRedraw property to True. This results in any output from the graphics methods being saved in memory as well as being drawn to the screen. They are now persistent and will remain visible when the form is hidden then redisplayed.
The other technique is to place the calls to the methods in the form's Paint event procedure. This event is triggered when the form is first displayed on screen and then again each time it needs to be redrawn after having been covered or minimized. To see this in action, move the line of code that calls the Circle method from the Click event procedure to the form's Paint event procedure. Now, the circle is displayed when the program starts and remains visible when the form is minimized and restored.
When AutoRedraw is set to True the form's Paint event procedure is never called. Which of these techniques you should use depends on the details of your program. Setting AutoRedraw to True usually simplifies programming, but it consumes extra memory and, particularly for complex graphics, can slowe down screen display.
Remember that the question of persistence applies only to the output of the graphics methods. Controls, bitmaps, and metafiles are always persistent.
Visual Basic Programming Tips 32
Getting Colors from a PictureBox Control
A Picture Box control can display various kinds of graphics images. Using the technique presented here, you can determine the color at any point in the image.
The Point method returns the color at a specified X,Y location in a Picture Box control. To get the color under the mouse pointer, put the code in the MouseMove event procedure for the control. Because the MouseMove procedure is passed the current X,Y coordinates of the pointer, this code is simple:
Private Sub Picture1_MouseMove(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
Dim rgb As Long
rgb = Picture1.Point(X, Y)
End Sub
The value returned by the point method is a type Long that encodes the RGB value for the color. To be useful, this encoded value must be separated into the individual R, G, and B components, each of which is an integer in the range 0-255 decimal or 00-FF in hexadecimal. Expressed in hexadecimal notation, the value returned by Point is:
00BBGGRR
Hexadecimal notation makes it easy to perform the extraction. If rgb is the value returned by the Point method, this extraction is performed as follows:
red = rgb Mod &H100
green = (rgb \ &H100) Mod &H100
blue = (rgb \ &H10000) Mod &H100
The final MouseMove event procedure is shown here. To see this in action, create a Standard EXE project and place a Picture Box and a Text Box on the form. Load an image into the Picture Box then run the project. You'll see that when the mouse moves over the image the Text Box displays the RGB values of the underlying pixel.
Private Sub Picture1_MouseMove(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
Dim rgb As Long
Dim r As String, g As String, b As String
rgb = Picture1.Point(X, Y)
r = CStr(rgb Mod &H100)
g = CStr((rgb \ &H100) Mod &H100)
b = CStr((rgb \ &H10000) Mod &H100)
Text1.Text = "R " & r & ", G " & g & ", B " & b
End Sub
You could place the same code in the MouseDown event procedure if you want to see the RGB values for only pixels that you click.
Visual Basic Tips
Sizing a Form's Interior
It's easy enough to set a form's size in code using its Width and Height properties, but this sets the form's outer size including borders and title bar. What if you want to set the form's interior area to a specific size, for example to precisely fit a Picture Box control? This tip shows you how.
You may think that you can change the form's ScaleWidth and ScaleHeight properties but this does not work. Setting these properties does not change the form interior's size but rather changes the units of measure used for graphics operations on the form. You can, however, read these properties to determine the size of the form's interior. This permits use of the following strategy:
Get the Form.Width value. This is the overall width of the form including borders.
Get the Form.ScaleWidth value. This is the width of the interior of the form.
Subtract the second value from the first. The result is the amount that the form's borders contribute to its overall width. Call this value deltaX.
Take the desired internal width and add deltaX to it. This is the overall width that the form will need in order to have the desired internal width.
Set the Form.Width property to the value obtained in step 4.
The same process is used for the height. To see this technique in action:
Start a new Standard EXE project.
Place a Picture Box control on the form and set its AutoSize property to True. This causes the Picture Box to automatically resize itself to fit the picture loaded into it.
Load an image into the Picture Box control.
Place the following code in the Form's Load event procedure.
Private Sub Form_Load()
Dim deltaX As Single, deltaY As Single
Picture1.Move 0, 0
deltaX = Me.Width - Me.ScaleWidth
deltaY = Me.Height - Me.ScaleHeight
Me.Height = Picture1.Height + deltaY
Me.Width = Picture1.Width + deltaX
End Sub
When you run the project you will see that the image is moved to the top left corner of the form and the form is resized to precisely fit the Picture Box. You can verify this by using the mouse to expand the form, revealing that the borders of the form and the Picture Box coincide exactly.
This technique requires that the ScaleMode, ScaleWidth, and ScaleHeight property of the form not be changed from the default values. This is because the size calculations depend on both the ScaleWidth, and ScaleHeight properties being in the default unit of twips.
Visual Basic Tips 33
Encoding and Decoding Passwords
It's not uncommon for programs to require some form of password protection. When a password is stored, in the registry for example, you do not want it in readable form because many users have the technical know-how to access the registry. Likewise, passwords are sometimes sent as part of a Web GET request. Some form of simple encryption should be used, as explained here.
There are various complex encryption alorithms available, but sometimes simple is better. A simple ASCII-value transposition is sometimes all that is needed. For example, suppose the password is "ajax7." You could encode it as follows:
1. The ASCII value of "a" is 97. Add 1 to get 98, which is the ASCII value of "b"
2. The ASCII value of "j" is 106. Add 1 to get 107, which is the ASCII value of "k"
3. The ASCII value of "a" is 97. Add 1 to get 98, which is the ASCII value of "b"
4. The ASCII value of "x" is 97. Add 1 to get 98, which is the ASCII value of "y"
5. The ASCII value of "7" is 55. Add 1 to get 56, which is the ASCII value of "8"
The encoded password is therefore "bkby8". The process of decoding is simply the reverse, subtracting 1 from each ASCII value. Visual Basic procedures for encoding and decoding passwords using this algorithm are shown here. It is assumed that passwords are limited to letters and numbers.
Public Function EncodePassword(pw As String) As St
The WebBrowser control is a powerful tool useful in many Web-related projects. In effect, this control provides the browser functionality of Internet Explorer in a software component that you can incorporate in your projects. In some applications it is desirable to customize the WebBrowser control's behavior. While some customization is possible through the control's properties and events, this is sometimes not sufficient. Microsoft has made a tool available that permits certain WebBrowser customizations, specifically having to do with accelerator keys and context (right-click) menus. You can disable context menus, and can disable all or selected accelerator keys. The tool is called WBCustomizer and is available by downloading WBCustom.exe from http://support.microsoft.com/support/kb/...3/2/35.ASP and executing it to extract WBCustomizer.dll and a sample project.
To use the WBCustomizer object, you must register the DLL as follows using the Run command:
regsvr32 wbcustomizer.dll
Use the full path to the DLL file as required. In your Visual Basic project the component must be selected using the References dialog box in order to permit early binding. Then, using the object is relatively simple. First, declare a variable to reference the object and create an instance of it:
Dim CustomWB As WBCustomizer
Set CustomWB = New WBCustomizer
Then, set the object properties to specify the desired customization, and associate it with the WebBrowser control. This code assumes that the WebBrowser control is named WebBrowser1. It turns off all context menus and accelerator keys:
With CustomWB
.EnableContextMenus = False
.EnableAllAccelerators = False
Set .WebBrowser = WebBrowser1
End With
The sample Visual Basic project that comes with the DLL provides examples of disabling specific accelerator keys.
Visual Basic Programming Tips 7
Avoid using the Internet Transfer Control
In theory the Internet Transfer Control is the best thing since sliced bread. In reality I consider it a real pain in the neck. Even though the latest version is vastly improved over the earlier releases, it is still buggy. Sometimes the OpenURL method does not retrieve an entire page. Also, I find that trying to use this control in a program that also uses automation to work with Office components often leads to weird and impossible to solve bugs. If you need to retrieve HTML pages, you can do so easily by calling some functions in the WinInet library. Without going into the details, here's how. First, put the following declarations in your project (in a code module):
Public Const INTERNET_OPEN_TYPE_PRECONFIG = 0
Public Const INTERNET_OPEN_TYPE_DIRECT = 1
Public Const INTERNET_OPEN_TYPE_PROXY = 3
Public Const scUserAgent = "VB OpenUrl"
Public Const INTERNET_FLAG_RELOAD = &H80000000
Public Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" _
(ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
ByVal sProxyBypass As String, ByVal lFlags As Long) As Long
Public Declare Function InternetOpenUrl Lib "wininet.dll" Alias "InternetOpenUrlA" _
(ByVal hOpen As Long, ByVal sUrl As String, ByVal sHeaders As String, _
ByVal lLength As Long, ByVal lFlags As Long, ByVal lContext As Long) As Long
Public Declare Function InternetReadFile Lib "wininet.dll" _
(ByVal hFile As Long, ByVal sBuffer As String, ByVal lNumBytesToRead As Long, _
lNumberOfBytesRead As Long) As Integer
Public Declare Function InternetCloseHandle Lib "wininet.dll" _
(ByVal hInet As Long) As Integer
Then, here's a function that returns the entire text of the specified URL:
Private Function GetHTMLFromURL(sUrl As String) As String
Dim s As String
Dim hOpen As Long
Dim hOpenUrl As Long
Dim bDoLoop As Boolean
Dim bRet As Boolean
Dim sReadBuffer As String * 2048
Dim lNumberOfBytesRead As Long
hOpen = InternetOpen(scUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
hOpenUrl = InternetOpenUrl(hOpen, sUrl, vbNullString, 0, INTERNET_FLAG_RELOAD, 0)
bDoLoop = True
While bDoLoop
sReadBuffer = vbNullString
bRet = InternetReadFile(hOpenUrl, sReadBuffer, Len(sReadBuffer), lNumberOfBytesRead)
s = s & Left$(sReadBuffer, lNumberOfBytesRead)
If Not CBool(lNumberOfBytesRead) Then bDoLoop = False
Wend
If hOpenUrl <> 0 Then InternetCloseHandle (hOpenUrl)
If hOpen <> 0 Then InternetCloseHandle (hOpen)
GetHTMLFromUrl = s
End Function
Visual Basic Programming Tips 8
Send raw data to the printer port
While Visual Basic makes some things very easy, other things are ridiculously difficult. For example, sending raw data to the printer port seems to be impossible. When I say "raw," I mean really raw - I am not talking about unformatted text. I wanted to make the printer port's 8 data lines output a specific bit pattern, such as 00000101, under program control. My task was to use the computer to control a piece of laboratory equipment that was equipped with a parallel interface, but this technique might also be applicable for programming the many other types of parallel port devices, such as external mass storage, that are available today. I tried a variety of Windows API calls without success and finally realized that this was one of those few times when going outside of Visual Basic is unavoidable. The solution lay in Visual C++, which has the _outp() function to send a byte directly to a hardware port (like Quick Basic's old out() statement). I compiled the code into a DLL called PPORT.DLL, which you can download in ZIP format by clicking here. Put the DLL in the \Windows\System folder, then declare the function in your Visual Basic program as follows:
Declare Function SendByteToPort Lib "pport.dll" (p As Integer, b As Integer) As Integer
The argument p is the port number, which for LPT1: is &H378. The argument b is the value, in the range 0-255, to be sent. The function's return value can be ignored. Be careful when using this function, because by writing directly to the hardware it bypasses the Windows safety nets. You can get into all sorts of trouble if you are not careful! I have had reports that this does not work under Windows 2000.
Since I wrote the above programmer Kris Tilly has informed me that you can send raw data to the printer port without resorting to a DLL written in C++, but rather by calling Windows API functions from your Visual Basic program. I have not tested this, but Kris says it works fine.
Public Type DOCINFO
pDocName As String
pOutputFile As String
pDatatype As String
End Type
Public Declare Function ClosePrinter Lib "winspool.drv" (ByVal _
hPrinter As Long) As Long
Public Declare Function EndDocPrinter Lib "winspool.drv" (ByVal _
hPrinter As Long) As Long
Public Declare Function EndPagePrinter Lib "winspool.drv" (ByVal _
hPrinter As Long) As Long
Public Declare Function OpenPrinter Lib "winspool.drv" Alias _
"OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, _
ByVal pDefault As Long) As Long
Public Declare Function StartDocPrinter Lib "winspool.drv" Alias _
"StartDocPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, _
pDocInfo As DOCINFO) As Long
Public Declare Function StartPagePrinter Lib "winspool.drv" (ByVal _
hPrinter As Long) As Long
Public Declare Function WritePrinter Lib "winspool.drv" (ByVal _
hPrinter As Long, pBuf As Any, ByVal cdBuf As Long, _
pcWritten As Long) As Long
Dim lhPrinter As Long
Dim lReturn As Long
Dim lpcWritten As Long
Dim lDoc As Long
Dim sWritteRdata As String
Dim MyDocInfo As DOCINFO
lReturn = OpenPrinter(Printer.DeviceName, lhPrinter, 0)
If lReturn = 0 Then
MsgBox "The Printer Name you typed wasn't recognized."
Exit Sub
End If
MyDocInfo.pDocName = "AAAAAA"
MyDocInfo.pOutputFile = vbNullString
MyDocInfo.pDatatype = vbNullString
lDoc = StartDocPrinter(lhPrinter, 1, MyDocInfo)
Call StartPagePrinter(lhPrinter)
For I = 1 To NumCopies
sWritteRdata = ""
lReturn = WritePrinter(lhPrinter, ByVal sWritteRdata, _
Len(sWritteRdata), lpcWritten)
lReturn = EndPagePrinter(lhPrinter)
Next I
lReturn = EndDocPrinter(lhPrinter)
lReturn = ClosePrinter(lhPrinter)
Visual Basic Programming Tips 9
Reduce the size of Visual Basic distribution files.
The Visual Basic Application Setup Wizard makes it easy to create a set of distribution files for your program. Given all of the support files that a Visual Basic program requires, however, distribution files tend to be rather large. This is a special concern when creating distribution files for download. It is important to ensure that your distribution files are as compact as possible. Yet, the Setup Wizard will sometimes include files in the distribution package that you are sure - or almost sure - that your program doesn't need. Why is this?
The problem is that the Setup Wizard is not smart enough to determine which software components your program actually needs, but can only detect those components that were available within the Visual Basic development environment at the time the program was compiled. For example, if the "SooperDooperGrid Control" was checked in the Components dialog box, its files will be included in the distribution files even though your program does not use it. Therefore, you need to remove all of the components that your program does not use from the Components dialog box before compiling your program. Fortunately, Visual Basic is pretty good at preventing you from removing components that are in use. I shrank a 2.5MB download to 1.6MB using this technique, so the gains can be significant.
Visual Basic Programming Tips 10
Reversing character order in a string
The function presented here is passed a string and returns the string with its character order reversed. "ABC" becomes "CBA" etc. Note that Visual Basic 6 has its own function StrReverse that does the same thing, so this is for users of earlier versions of Visual Basic.
Public Function Reverse(s As String) As String
' Returns a string with the characters
' in s in reverse order.
Dim buf As String, i As Long
If Len(s) < 2 Then
Reverse = s
Exit Function
End If
buf = ""
For i = Len(s) To 1 Step -1
buf = buf & Mid(s, i, 1)
Next i
Reverse = buf
End Function
Visual Basic Programming Tips 11
Creating Synthetic Keystrokes
Visual Basic has a statement SendKeys that can be used to "generate" certain keystrokes just as if they had been pressed by the user. However this function is limited in that it cannot be used to simulate all possible keystroke combinations. I discovered this recently when trying to capture screens from within a Visual Basic program - you cannot send Alt+PrintScr using SendKeys. I was informed by reader Matt Hart that you can accomplish essentially the same thing with an API function, Keybd_Event which does not have the limitations of SendKeys. This function "synthesizes" a keystroke, and it does a pretty good job because this is the same function that is called by the keyboard’s hardware interrupt handler. As far as I can tell, the operating system has no way of knowing whether a keystroke came from someone's grubby little finger actually hitting the keyboard or was generated in software using Keybd_Event. Here’s the function declaration:
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal _
bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Without going into all the details, here’s how you can capture screens to the clipboard. First declare the following constant:
Const VK_SNAPSHOT As Byte = &H2C
Then capture the active Visual Basic window to the clipboard with:
Call keybd_event(VK_SNAPSHOT, 0, 0, 0)
Or capture the entire screen (hiding the Visual Basic window first, if desired) with:
Call keybd_event(VK_SNAPSHOT, 1, 0, 0)
You’ll find a sample program that uses this code to capture AVI video images on Matt’s web page at http://www.blackbeltvb.com/
Visual Basic Programming Tips 12
Validating web links
You can be pretty sure that www.microsoft.com will always be a valid link, but many other web links tend to come and go as their authors move on, get bored, or whatever. If you maintain a web page with lots of external links, it is in your interest to ensure that the links are functioning. If lots of the links on your page are dead, your visitors will not be impressed! Many web page design tools, such as FrontPage, have a "validate links" command but I have found these to be fairly useless. They tell you which links are OK and which are not, but they do not permit you to do anything about the bad links (other than manually removing them from the pages). I needed a better solution.
I keep my links database in an Excel worksheet - one column for URL, one for Title, etc. I have written an Excel Basic macro that reads through the table and writes the HTML code for each of my links pages. I decided to write a Visual Basic program that would:
Use DDE to read a URL from the links worksheet.
Attempt to connect to the URL and note whether the URL is valid or not.
Use DDE to put the label "OK" or "BAD" in the worksheet to indicate whether the URL was good or not.
Continue for all the links in the worksheet.
Then, I would re-write the Excel Basic macro so that a link will be written to the HTML file only if the URL is marked as "OK." This approach has worked very well for me. Here's the code. Create a form with a Text Box, an Internet Transfer control, four Label controls, and a control array of two Command Buttons. In the General section put the following code:
Option Explicit
Const FILENAME = "c:\documents\stamps\links.xls"
Dim XLObj As Excel.Application
Change the FILENAME constant to point to your worksheet file. Here is the code for the Form_Load and Command1_Click event procedures:
Private Sub Command1_Click(Index As Integer)
Select Case Index
Case 0 'Start
Command1(0).Enabled = False
Call CheckLinks
Command1(0).Enabled = True
Case 1 ' Quit
End
End Select
End Sub
Private Sub Form_Load()
' Create the Excel object and open the worksheet.
Set XLObj = CreateObject("Excel.Application")
XLObj.Workbooks.Open FILENAME
Inet1.Protocol = icHTTP
End Sub
The bulk of the action goes on in the CheckLinks() procedure.
Public Sub CheckLinks()
Dim row As Integer, url As String
Dim buf As String, msg As String, fnf As Integer
Dim snf As Integer, tout As Integer, ok As Integer
On Error Resume Next
' Make row equal to the Worksheet row where
' your data starts.
row = 4
tout = 0
fnf = 0
ok = 0
snf = 0
' Minimize the form.
Form1.WindowState = 1
Do
' I keep URLs in column 3 © of the worksheet.
url = XLObj.Cells(row, 3)
' If it's empty we are done.
If url = "" Then Exit Do
' Try to open the URL.
Text1.Text = Inet1.OpenURL(url)
DoEvents
' If the URL returned any text, put the
' first 50 characters in a buffer. Error
' messages will be found here.
If Len(Text1.Text) > 50 Then
buf = Left(Text1.Text, 50)
Else
buf = Text1.Text
End If
' Catch a time out error.
If Err = 35761 Then
msg = "Timed out"
tout = tout + 1
Err.Clear
' If nothing is returned it usually means
' that the server was not found.
ElseIf Text1.Text = "" Then
msg = "Server not found"
snf = snf + 1
' If error 404 is returned from the URL
' it means the server was found but
' the requested file was not present.
ElseIf InStr(1, buf, "404") Then
msg = "File not found"
fnf = fnf + 1
' Otherwise the link is OK.
Else
msg = "OK"
ok = ok + 1
End If
' Put the result in column 5 of the worksheet.
XLObj.Cells(row, 5) = msg
' Move to the next row.
row = row + 1
' Display current status on form.
Form1.Caption = ok + fnf + snf + tout
Label1.Caption = "OK: " & ok
Label2.Caption = "File not found: " & fnf
Label3.Caption = "Server not found: " & snf
Label4.Caption = "Timed out: " & tout
Loop While True
' When all links checked, restore the form.
Form1.WindowState = 0
' Close the worksheet.
XLObj.Workbooks.Close
' Delete the object.
Set XLObj = Nothing
' Display a summary of results.
buf = "OK: " & ok & vbCrLf
buf = buf & "Server not found: " & snf & vbCrLf
buf = buf + "File not found: " & fnf & vbCrLf
buf = buf & "Timed out: " & tout
MsgBox (buf)
End Sub
The program takes a while to run - a couple of hours to check a few hundred links - but because it is running in the background you can continue to use your system for other tasks. You can download a ZIP file containing the Visual Basic project by clicking here.
Note: Since originally creating this project I have made some modifications that increase its reliability and flexibility. First, the links information is kept in an Access database rather than in an Excel workbook. The Visual Basic program uses ADO to read and write the data table. Second, I have abandoned the Internet Transfer control in favor of using the WinINet library to implement the http protocol. Details of using WinINet are presented here.
Visual Basic Programming Tips 13
Easy Creation of Database Connection Strings
For a Visual Basic program to connect to a data source, a connection string is often required. This string specifies the data source, user name, security information, and other aspects of the data connection. Here's an example of a fairly simple connection string:
Provider=Microsoft.Jet.OLEDB.3.51;Persist Security Info=False;User ID=AliceK;Data Source=C:\data\Northwind.mdb
Who can remember all the details required to write connections strings? I know I can't! Visual Basic can help you, as follows:
Make sure the ADO Data Control is displayed in the Visual Basic toolbox. This is not the standard Data control, but is identified by a tooltip that says "ADODC". If it is not present you will have to add it by pressing Ctrl+T to open the Components dialog box then putting a checkmark next to "Microsoft ADO Data Control 6.0."
Use the usual techniques to place an ADO Data Control on a form.
In the Properties window, select the ConnectionString property then click the ellipses button (...) in the right-hand column.
The control's property page displays, Select the Use Connection String option, then click the Build button.
The next dialog box contains several tabs on which you can enter and select the options for the database connection, including the provider, the DSN (if you are using one), the user name and password, and the read/write permissions. You can also test the connection from this dialog box.
When you are finished defining the connection, click OK. You will return to the control's Property Page. The generated connection string will be inserted in the relevant text box.
Copy the generated connection string from the Property Page to your code.
Delete the ADO Data Control from the form.
This method for creating connection strings is a lot faster and results in fewer errors than doing it manually.
Visual Basic Programming Tips 14
Aligning Controls at a Specific Position
The Visual Basic form designer makes it easy to align a group of controls with one member of the group. Simply select the controls by holding down shift while clicking each control, being sure to click the target control (the one the others will be aligned to) last. Then, select Format|Align, then select Lefts for vertical alignment or Tops for horizontal alignment.
But what if you want to align controls at a specific position rather than to an existing control? For example, you might want to align several TextBox controls so their left edges are all exactly 50 twips from the left edge of the form. Here's how:
Select all the controls as described above. It does not matter which control is selected last.
In the Properties window, enter the desired distance from the left edge of the form in the Left property. Or, to align at a fixed distance from the top of the form, enter the distance in the Top property.
This technique can be extended to other design tasks. When more than one control is selected, the Properties window displays only those properties that are common to all the controls. Changing a property value is automatically applied to all the selected controls.
Visual Basic Programming Tips 15
Don't Forget the Tag Property
Most Visual Basic programmers never make use of the Tag property, available with most controls. After all, this property doesn't do anything so why bother with it? The truth is, it can be very useful in a variety of situations. You can store any information you want in a control's Tag property, and use that information any way you want.
One example is when you create a control array of CommandButton controls. Programmers usually use the Index argument that is passed to the Click event procedure to identify which button in the array was clicked. By assigning each button a descriptive Tag property you can use that instead and have more readable code. Here's the code for a simple example:
Private Sub Command1_Click(Index As Integer)
Select Case Command1(Index).Tag
Case "Exit"
' Code to exit the program goes here.
Case "Cancel"
' Code to cancel goes here.
End Select
End Sub
Another way to use the Tag property is when working with a TreeView control. A TreeView is made up of nodes, and it can be useful to have some data associated with each node in addition to the text it displays. This is easily accomplished by using the Tag property of each Node object.
Visual Basic Programming Tips 16
Do You DoEvents?
Lots of programmers don't even know about Visual Basic's DoEvents function. This is not surprising because few Visual Basic programs need it. DoEvents returns control to the operating system temporarily, allowing it to process other events that may have occurred. In my experience, the only time DoEvents is needed is when a program has code that takes a long time to execute, such as certain complex mathematical calculations. By calling DoEvents at strategic locations in your code you can improve program responsiveness.
To see what I mean, create a Standard EXE project in Visual Basic and place one CommandButton and one TextBox on the form. Then, put the following code in the Command Button's Click event procedure:
Private Sub Command1_Click()
Dim i As Long, j As Long
For i = 1 To 100
Text1.Text = i
For j = 1 To 100000
Next
Next
Text1.Text = "Done"
End Sub
You can see that the code has one loop within another, loops that will take a few seconds to complete (You may want to adjust the value that the inner loop counts to depending on the speed of your system). Each time the outer loop iterates, the current value of i is displayed in the text box. When the loops are finished, "done" is displayed.
What actually happens when you run the program, however, is that the text box does not change until "done" is displayed. The problem is that the system was so busy executing the loops that the requests to display i in the text box got stalled in Windows queue. When the loops were finished, all these requests were processed, too quickly for you to see on the screen.
Now, place a call to DoEvents in the code, just after the Text1.Text = i statement. When you run the program you will see that the text box "counts up" the values of i, just as you would expect. Calling DoEvents frees up the system to process the request, then returns control to the Visual Basic program.
DoEvents is not without potential problems. For example, if you call DoEvents from a procedure you must be sure that the same procedure cannot be called again before execution returns from the first call - otherwise unpredictable results may occur. Likewise, DoEvents should be avoided if other applications could interact with the procedure in unforeseen ways. Use of a Timer control or isolation of long-running code in an ActiveX component are two other approaches to improving program responsiveness.
Visual Basic Programming Tips 17
Screen Capture in Visual Basic
Capturing images of the screen is useful in many situations, such as when documenting computer glitches or writing technical manuals and software documentation. Can you capture a screen image from a Visual Basic program? You bet. There's a complex way, involving Windows device contexts and the API, but it would be easier to use Window's built-in screen capture capability. Pressing the PrintScrn button copies the entire screen to the clipboard, while Alt+PrintScrn copies just the active window. All a Visual Basic program would need to do is to "press" this key.
Unfortunately, you cannot use Visual Basic's SendKeys for this purpose - it just won't work with PrintScrn. You must use the Keydb_Event API function. This function synthesizes a keystroke, and because it is the same function that is called by the keyboard’s hardware interrupt handler, it should do a good job of fooling an application - or Windows in this case - into thinking that the key was actually pressed. The function declaration is as follows:
Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, _
ByVal bScan As Byte, ByVal dwFlags As Long, _
ByVal dwExtraInfo As Long)
I can't go into all the details of this statement, but here’s how to capture screens to the clipboard. Start by declaring the following constant:
Const VK_SNAPSHOT As Byte = &H2C
Then you capture the active window to the clipboard with this code:
Call keybd_event(VK_SNAPSHOT, 0, 0, 0)
To capture the entire screen you use the following:
Call keybd_event(VK_SNAPSHOT, 1, 0, 0)
Once the screen image is on the clipboard, you will use the Clipboard object's GetData method to retrieve it as required by your application. The following code captures the screen and displays it in a PictureBox control, from where it can be saved to disk is desired.
Clipboard.Clear
Call keybd_event(VK_SNAPSHOT, 0, 0, 0)
DoEvents
Picture1.Picture = Clipboard.GetData(vbCFBitmap)
Please note two things about this code. First, you should call Clipboard.Clear before "pressing" PrintScrn to remove any old data from the clipboard. Second, you should call the Visual Basic DoEvents function before trying to retrieve the captured image, to allow Windows the time to process the keybd_event call.
Visual Basic Programming Tips 18
Determining Drive Type
A disk drive can be one of several types. Sometimes a program needs to be aware of the type of the drive in order to know what it can and cannot do. For example, a program cannot write to a CD-ROM drive. Likewise, if a drive a removable a program cannot assume anything about what files are on the drive. You can use an API function, GetDriveType, to easily determine the type of a drive. This function has the following declaration, which must be included in your project:
Declare Function GetDriveType Lib "kernel32" Alias _
"GetDriveTypeA" (ByVal nDrive As String) As Long
The function's argument is s string identifying the drive's root folder - for example, "c:\" or "f:\". The return value is a type Long, one of the following depending on the drive type. This list includes the constants that are usually defined for use in the program.
DRIVE_REMOVABLE = 2
DRIVE_FIXED = 3
DRIVE_REMOTE = 4
DRIVE_CDROM = 5
DRIVE_RAMDISK = 6
If the function returns any other value it means the drive was not recognized (that is, does not exist or is not available, as in a network drive).
The following Visual Basic procedure illustrates the use of this API function. The argument to this procedure must be in the form required by the GetDriveType function, that is the drive letter followed by ":\". Of course the API function declaration and the constant definitions must be included in any program that uses this procedure.
Private Sub DriveType(dr as string)
Dim dr As String
Dim drivetype as Long
drivetype = GetDriveType(dr)
Select Case drivetype
Case DRIVE_REMOVABLE:
MsgBox "Drive " & dr & " is a removable drive"
Case DRIVE_FIXED:
MsgBox "Drive " & dr & " is a fixed (hard) drive"
Case DRIVE_REMOTE:
MsgBox "Drive " & dr & " is a remote (network) drive"
Case DRIVE_CDROM:
MsgBox "Drive " & dr & " is a CD-ROM drive"
Case DRIVE_RAMDISK:
MsgBox "Drive " & dr & " is a RAM drive"
Case Else:
MsgBox "Drive " & dr & " was not recognized"
End Select
End Sub
Visual Basic Programming Tips 19
Filtering TextBox Input
The TextBox control is commonly used to accept keyboard input from the user. In some situations you may want to restrict what the user can type to ensure that invalid data is not entered. For example:
- A TextBox for ZIP code could be restricted to accepting digits (and, if ZIP+4 is being used, the hyphen).
- A TextBox for entry of a person's first name could be restricted to letters.
- A TextBox for an email address could be restricted to letters, digits, and the period and @ characters.
To restrict entry into a TextBox you use its KeyPress event procedure:
Private Sub Text1_KeyPress(KeyAscii As Integer)
End Sub
The KeyAscii argument is a code identifying the key that was pressed as an ASCII value. Code in the KeyPress event procedure can examine the value of KeyAscii to see if a permitted character was entered. If so, do nothing and the character will be passed through to the TextBox. If the key is not permitted, set KeyAscii to 0 and the keypress is cancelled. KeyPress should always pass through the BackSpace key (ASCII value = 8) to permit the user to delete erroneous characters.
The following KeyPress event procedure lets only the digits 0-9 through, plus Backspace. The ASCII values for the characters 1 though 9 are 48-57.
Private Sub Text1_KeyPress(KeyAscii As Integer)
If KeyAscii = 8 Then Exit Sub
If KeyAscii < 48 Or KeyAscii > 57 Then
Beep
KeyAscii = 0
End If
End Sub
Note that the KeyPress event procedure will not intercept editing keys such as Del or the arrow keys, so you do not need to make provision to pass them through in your code.
Visual Basic Programming Tips VB6 20
Counting Strings
Visual Basic makes it easy to find one string within another using the InStr function. But what about counting the number of times one string occurs within another? One use for this is when performing Web searches. The more times a term of interest appears on a Web page, the more likely the page is to be relevant.
It's not too difficult to program string counting yourself. You can use the InStr function, calling it repeatedly until it returns 0. With each call after the first one, update the Start position to the location of the last "hit" plus one so that InStr will find the next occurrence of the target. Keep track of the hit count, and when InStr returns 0 you are done.
The code shown here shows how. The function is named CountSubstrings. You pass it the target string to be searched, the template you are looking for, and a binary value indicating whether you want text searches to be case-sensitive. Note that the code takes care of three "error" conditions that might occur:
- The target string is blank.
- The template string is blank.
- The template is longer than the target.
In any of these situations the function returns -1.
Public Function CountSubstrings(target As String, _
template As String, CaseSensitive As Boolean) _
As Integer
' Returns the number of times template occurs in target.
' Returns -1 if either string is blank or if template
' is longer than target. If CaseSensitive is true, performs
' a case-sensitive comparison for text. If false, the
' comparison is case-insensitive.
Dim pos1 As Integer, pos2 As Integer, count As Integer
If Len(target) = 0 Or Len(template) = 0 Or _
Len(template) > Len(target) Then
CountSubstrings = -1
Exit Function
End If
count = 0
pos2 = 1
Do
If CaseSensitive Then
pos1 = InStr(pos2, target, template, vbBinaryCompare)
Else
pos1 = InStr(pos2, target, template, vbTextCompare)
End If
If pos1 > 0 Then
count = count + 1
pos2 = pos1 + 1
End If
Loop Until pos1 = 0
CountSubstrings = count
End Function
Visual Basic Programming Tips 21
Create a Temporary File Name
The only requirement for a temporary file name is that the file name not already be in use in the folder where the file will be created. The function presented here takes a path as its one argument - the path where you nee the temporary file - and returns a filename (with path) that is not present in the specified folder and can therefore be safely used for a temporary file. If a blank string is passed as the path argument, the application path (App.Path) is used. The function returns a blank string on error. The function does not verify that the path that is passed to it is valid.
The operation of the code is simple. It starts with the value 1 and makes a file name from it (1.tmp). It then checks to see if this file already exists in the specified folder using the Dir function. If not, the name is suitable and is returned by the function. If the file exists, the value is increased and the new file name (2.tmp, 3.tmp, etc.) is tried until an unused name is found.
Public Function MakeTempFileName(path As String)
' Returns a filename (with path) that is not
' already in use in the indicated path. Name
' has the form path\1.tmp, path\2.tmp, etc.
' If path is blank then App.Path is used.
' Does not verify that path is valid.
Dim x As Integer, s As String
If path = "" Then path = App.path
' Be sure path ends with \.
If (Right(path, 1) <> "\") Then path = path & "\"
x = 0
Do
x = x + 1
s = path & x & ".tmp"
Loop Until Dir(s) = ""
MakeTempFileName = path & x & ".tmp"
End Function
Visual Basic Programming Tips 22
Getting and Using Screen Information
A Visual Basic program needs to be able to run on different systems. In particular, the video equipment is likely to differ from one system to another. Joe may have an older laptop with 800x600 resolution, while Alice is running a new 21 inch monitor at 1600x1200 pixels. For some programs these video differences may not be important, but in other cases you may want to make sure that your program makes the best possible use of the screen real estate and resolution. You can use the Screen object to obtain information about the display hardware.
Recall that Visual Basic uses twips for screen measurements, with 1 twip supposedly equal to 1/1440 inch. You can determine the screen's size in twips from two of the Screen object's properties, Screen.Width and Screen.Height. In theory you could use this information to determine the actual screen size as follows:
ScreenWidthInInches = Screen.Width/1440
ScreenHeightInInches = Screen.Height/1440
I have found, however, that the actual size of a twip varies a lot from system to system. On mine, for example, a twip is a lot closer to 1/1000 inch, and the above calculations will report my 21 inch monitor as being about. 9x12 inches! The twip values are accurate in terms of positioning items on the screen, though, and you can use them to position forms on the screen without going off the edge. This code, for example, centers a form on the screen:
Form1.Move (Screen.Width - Form1.Width) / 2, _
(Screen.Height - Form1.Height) / 2
Here's another example that positions the form at the lower right corner of the screen on any system (although some of the form may be covered by the Windows task bar):
Form1.Move Screen.Width - Form1.Width, Screen.Height - Form1.Height
Visual Basic Programming Tips 23
Maximizing and Evaluating Computational Performance
With today's multi-gigahertz computers, programmers are rarely concerned with the speed of computations. Code runs plenty fast, and the bottlenecks for program performance are much more likely to be related to network access and other factors that are beyond the programmer's control. At times, however, raw computation speed does become an issue. This is most likely to happen when you are performing complex recursive tasks such as certain mathematical calculations and text processing operations. In these situations, even minor improvements to your code speed are likely to have an impact on the program's usability. Shortening a computation from, say, 60 seconds to 40 seconds is quite noticeable to the user. Here are some coding tips to maximize the performance of long computations:
Do not use type Variant variables unless it is unavoidable.
For numeric variables, use the smallest type possible for the data. That is, for integer data use Byte in place of Integer, and use Integer in place of Long. For floating point data use Single in place of Double.
For string data, use fixed-length strings as opposed to variable length strings whenever possible.
Do not write to the screen during long computations.
Minimize procedure calls. If a section of code is executed many times, it is better to place it in-line than to place it in a separate procedure that must be called with the attendant processing overhead.
While writing your program you can use the Timer function to test the speed of calculations and help you in fine-tuning the code for maximum performance. The Timer function returns the number of seconds since midnight, accurate to 1/100 of a second. Set one variable equal to Timer just before calculations start, and set another variable equal to Timer when they are complete. A simple subtraction will tell you how long the computations took. Be sure to run your tests on a compiler version of the program and not in the IDE.
Visual Basic Programming Tips 24
Create Blinking Text on a Form
Blinking text is great way to call attention to something on the screen. It's easy to implement blinking text using Visual Basic's Timer control. This example shows you how to make a Label control blink, but the same approach could be used for other controls as well.
When you place a Timer control on a form, it does not display when the program runs - it's available "behind the scenes." The Timer control has two important properties. The Interval property determines how often the Timer "ticks." The value is in milliseconds, so a setting of 500 will give you a twice per second Timer which I think is a good rate for blinking text. The Enabled property determines whether the Timer is running (Enabled = True) or stopped (Enabled = False).
The actual blinking is done in the Timer event procedure, which fires each time the Timer "ticks." One approach is to toggle the Label control's ForeColor property between black, which makes it visible, and the value of its BackColor property, which makes the text invisible. You must check the current value of the ForeColor property and then set it accordingly, as shown here:
Private Sub Timer1_Timer()
If Label1.ForeColor = Label1.BackColor Then
Label1.ForeColor = vbBlack
Else
Label1.ForeColor = Label1.BackColor
End If
End Sub
You could also implement a different type of blinking, for example having the text alternate between red and green:
Private Sub Timer1_Timer()
If Label1.ForeColor = vbRed Then
Label1.ForeColor = vbGreen
Else
Label1.ForeColor = vbRed
End If
End Sub
To turn the blinking on or off, set the Timer control's Enabled property to True or False. When you turn the blinking off, you want to make sure that the text is not left in the invisible state, or with an inappropriate color. To do so, include code to set the ForeColor property to the desired color at the same time you turn the Timer control off:
Timer1.Enabled = False
Label1.ForeColor = vbBlack
Visual Basic Programming Tips 25
Using Command Line Arguments
Command line arguments date from the days of DOS when programs were run by entering the program name at the command line. Many programs could accept arguments placed after the program name, as shown here:
C>ProgramName argument1 argument2
These command line arguments could be read by the program and used according to the program's design. For example, some programs accepted an argument specifying the data file to open, and others accepted arguments that set certain program options. With the Windows operating system, where programs are started with a click of the mouse, the command line may seem to be a relic of the past, but it's not. Your Visual Basic programs can still use command line argument.
But Windows has no command line, so how can you pass command line arguments to a program? There are two ways. In the Visual Basic environment, when writing and testing your program, you display the Project Properties dialog box (Select Project|Properties) and, on the Make tab, you enter the arguments in the Command Line Arguments field. After the program is compiled you must use a shortcut to use command line arguments:
Create a shortcut for the compiled Visual Basic program.
Right-click the shortcut and select Properties.
In the Properties dialog box, select the Shortcut tab.
The Target field will contain the command used to start the program. This command will include the path to the compiled program plus the program name, for example "c:\program files\myprogram.exe".
Edit this text to include the command line argument(s) after the program name. Be sure to use a space between the name and the arguments, and put the arguments inside the quotes (if present). For example "c:\program files\myprogram.exe argument1 argument2".
Click OK to close the Properties dialog box.
Now, whenever this shortcut is used to start the program, the specified arguments will be passed to it. By creating more than one shortcut to the same program, you can start the program with different arguments depending on the situation.
In a Visual Basic program, the Command$ function returns a string containing any command line arguments that were passed when the program was started. The arguments (if more than one) are not parsed in any way - they are returned together in a single string. This string is blank if no arguments were passed. It is totally up to your program what it does, if anything, with the arguments it receives. Command line arguments are not for every programming situation, but they can provide some additional flexibility in some circumstances.
Visual Basic Programming Tips 26
Use Environment Variables
Environment Variables are maintained by the Windows operating system. Each variable consists of a name and a value. You can see the current environment variables by opening a Command Prompt window and typing Set. These variables are system-wide and can be used by any program. Many of them have to with the operating system, but you can create environment variables for use by your Visual Basic programs. Because such variables are available to all programs, they provide a convenient approach when you need to set options or operating parameters for multiple programs.
There are two ways to create or change an environment variable. In a batch file, such as AutoExec.bat, you use the Set command as follows:
Set varname=value
You can also open System from the Windows Control Panel. On the Advanced tab, click Environment Variables and use the dialog box tools to add, edit, or delete variables. Note that there are User variables, which are set for and can be changed by the currently logged on user, and System variables, which can be added/changed only by someone with administrator privileges (Windows NT/2000/XP).
To access environment variables in your Visual Basic program, you use the Environ function. There are two ways to use this function. If you pass the name of an environment variable the function returns the value of that variable, or a blank string if the variable is not defined. If you pass a number, the function returns the entire environment string (the variable name, equal sign, and value) of the variable at the specified position. If there is no variable at the specified position, the function returns a blank string. This code displays all environment variables in a Message Box:
Dim buf As String, msg As String, idx As Integer
idx = 1
Do
buf = Environ(idx)
msg = msg & buf & vbCrLf
idx = idx + 1
Loop Until buf = ""
MsgBox msg
By using the Environ function, your Visual Basic program can retrieve the values of any relevant environment variables and use this information as needed.
Visual Basic Programming Tips 27
Prevent Multiple Program Instances From Running
Many Windows programs, including programs created with Visual Basic, permit more than one instance, or copy, of the program to run at the same time. You can, for example, have two or more copies of Notepad running at the same time, editing different text files. There are situations in which permitting multiple instances of a program is not a good idea. A program might monitor a specific folder and keep track of new files that are placed there; there is never a need to have two instances of such a program running. Likewise, a program that is a front-end to a database should be restricted to one instance because having two or more copies running at the same time could lead to data errors.
To prevent multiple copies of a program from running, use the App.PrevInstance property. This property returns True if there is another instance of the program running. You can check this property in a program's Load event procedure. If it returns True, you can display a message to the user and, if desired, activate the running copy before terminating the new, duplicate instance. Here's a code example that you would place in the Load event procedure:
If App.PrevInstance Then
MsgBox "Another instance of this program is already open."
AppActivate App.Title
Unload Me
End If
Visual Basic Programming Tips 28
Avoid the End Statement
Visual Basic's End statement immediately terminates the program, and because it's so easy to remember a lot of programmers use it regularly for program termination. There's a problem with End, however, in that the program's forms are not unloaded properly. Specifically, each form's Unload event procedure is not triggered. If your program uses the Unload event procedure to save data, close files, and perform other cleanup tasks, then using End to terminate the program is clearly not a good idea.
The preferred way to terminate a program is to unload all of its forms. With a single form program this is not a problem - just execute
Unload Me
When a program has multiple forms, this can be more of a problem - particularly when you do not know which forms are open at the time the program is terminating. To address this task, you can use the Forms collection, which contains all of the program's currently loaded forms. The technique is to loop through the collection, unloading each form in turn until all have been unloaded. The result is that the program is terminated properly, with each form's Unload event rpocedure being triggered. Here's the code:
Public Sub TerminateProgram()
Static Unloading As Boolean
Dim idx As Integer
If Unloading Then Exit Sub
Unloading = True
For idx = Forms.Count - 1 to 0 Step -1
Unload Forms(idx)
Next idx
Unloading = False
End Sub
Note two things about this code. First, the forms are unloaded in reverse order, starting with the last one in the Forms collection. If you started with the first form in the collection, the form numbers would be reassigned by the Forms collection to fill in for the removed form, and the required code would be more complex. Second, a flag is set to prevent the unloading loop from executing if it is already in progress (if, for example, the TerminateProgram procedure was inadvertently called twice).
Remember that a form can block its own unloading by setting the Cancel argument to a non-zero value in the QueryUnload event procedure. In this case the procedure shown above may not be able to close all forms and terminate the program, and the user may have to close one or more forms manually.
Visual Basic Programming Tips 29
Clear all Text Boxes on a Form
You have probably see Web pages that contain many text fields, such as a form for online ordering. Such pages often have a Clear or Reset button that erases the text in all the fields so you can start over. You can implement a similar feature for Visual Basic forms, clearing all the Text Box controls on the form for the entry of new data.
To do so you will use the form's Controls collection. This collection contains an entry for every control on the form. By looping through the collection you can determine the type of each control using the TypeOf keyword. If a control is a Text Box, erase its text; if a control is not a Text Box, ignore it. Here's a procedure to clear all text Box controls on the form:
Public Sub ClearTextBoxes()
Dim c As Control
For Each c in Controls
If TypeOf c Is TextBox Then
c.Text = ""
End If
Next
End Sub
You can extend this technique to other controls. For example, this code clears (unchecks) all CheckBox controls on the form:
For Each c In Controls
If TypeOf c Is CheckBox Then
c.Value = False
End If
Next
Visual Basic Programming Tips 30
Implementing a Stack
A stack stores data in a last-in, first-out manner, and can be very useful in many areas of programming. This tip shows you how to implement a stack in Visual Basic. Traditionally a stack has two operations associated with it: Push to put something on the stack and Pop to retrieve something from the stack. Push always puts a data item on the top of the stack, and Pop always retrieves and removes whatever it on the top of the stack. Suppose you did the following Push operations in this order:
Push A
Push B
Push C
If you then did three Pops, you would retrieve C with the first Pop, B with the second, and A with the first. Note that a stack needs some way to signal when it is empty.
A stack is best implemented as a Class module in Visual Basic. Basically, a stack is an array that holds the data plus a pointer that indicates which position in the array is currently the "top" of the stack. In my Stack class I used a dynamic array that can expand as needed to hold new items that are pushed onto the stack. When items are removed by popping them from the stack, the array does not shrink in size to avoid the overhead of repeatedly calling ReDim.
The code for the Stack class is shown here. This implementation uses type Variant to enable the stack to hold both string and numeric data. You could also create stacks designed specifically for certain data types. Note that the Class_Initialize event procedure initializes the pointer variable to 0 and the data array to a size of 1. Also note that Pop returns Null when the stack is empty, and the program that is using a stack needs to check for this to verify that Pop has returned valid data.
Private data() As Variant
Private pointer As Long
Private Sub Class_Initialize()
pointer = 0
ReDim data(1)
End Sub
Public Function Pop()
If pointer > 0 Then
Pop = data(pointer)
pointer = pointer - 1
Else
Pop = Null
End If
End Function
Public Sub Push(value)
pointer = pointer + 1
If UBound(data) < pointer Then
ReDim Preserve data(pointer)
End If
data(pointer) = value
End Sub
To use the Stack class, create a instance of it using the usual Visual Basic syntax and then call Push and Pop as needed. The following code is from a demo program that you can use to verify how the Stack class operates. To run it, follow these steps:
1. Create a new Visual Basic Standard EXE project.
2. Place two Command Button controls on the form.
3. Create a new Class module and change its Name property to Stack.
4. Add the code listed above to this class module.
5. Add the code below to the form module.
Dim st As Stack
Private Sub Form_Load()
Set st = New Stack
End Sub
Private Sub Command1_Click()
Dim t As Single
t = Timer
Debug.Print "Pushed ", t
st.Push (t)
End Sub
Private Sub Command2_Click()
Debug.Print "Popped ", st.Pop
End Sub
When you run the program, you can click the command buttons to push and pop values (provided by the Timer function) on and off the stack. In a real-world program you'll find numerous uses for the Stack class, whenever you need to temporarily store data on a last-in, first-out basis.
Visual Basic Programming Tips 31
Converting Numbers Between Decimal and Binary
While computers may use binary numbers internally, programmers usually use decimal and sometimes hexadecimal notation in their programs. Even so, it can still be useful to see the binary representation of a number, particularly when you are working with logical operators or doing bitwise comparisons. Converting a binary representation to decimal reverses the process. This tip shows you how to convert a positive integer into a string containing its binary representation, and then back again.
Binary notation is based on powers of 2, and this fact makes the conversion process surprisingly simple. It works as follows (for decimal value X):
1. Calculate X Mod 2. The result will be either 0 or 1. This is the first (right-most) binary digit.
2. Divide X by 2, discarding any remainder (in other words, perform an integer division using the \ operator).
3. If X is 0 you are done. Otherwise return to step 1 to determine the next binary digit.
Let's work this through for the decimal value 13:
1. 13 Mod 2 is 1 so the first binary digit is 1.
2. 13 \ 2 is 6.
3. 6 Mod 2 is 0 so the second binary digit is 0.
4. 6 \ 2 is 3.
5. 3 Mod 2 is 1 so the third binary digit is 1.
6. 3 \ 2 is 1.
7. 1 Mod 2 is 1 so the fourth binary digit is 1.
8. 1 \ 2 is 0 so you are done. The result is 1101, the binary representation of 13
The code is presented here as a Visual Basic function. In addition to the value to be converted, the function takes an argument specifying the minimum number of digits in the binary value. If the actual value has fewer digits that this value, leading zeros are added to make up the difference.
Public Function DecimalToBinary(DecimalValue As Long, _
MinimumDigits As Integer) As String
' Returns a string containing the binary
' representation of a positive integer.
Dim result As String
Dim ExtraDigitsNeeded As Integer
' Make sure value is not negative.
DecimalValue = Abs(DecimalValue)
' Construct the binary value.
Do
result = CStr(DecimalValue Mod 2) & result
DecimalValue = DecimalValue \ 2
Loop While DecimalValue > 0
' Add leading zeros if needed.
ExtraDigitsNeeded = MinimumDigits - Len(result)
If ExtraDigitsNeeded > 0 Then
result = String(ExtraDigitsNeeded, "0") & result
End If
DecimalToBinary = result
End Function
Converting a binary representation to a decimal number is the reverse of the process in the above function. Each digit in a binary value represents a power of 2, starting with 2^0 for the first (right-most) binary digit, 2^1 for the second digit, and so on. Note that any number to the 0 power is by definition 1, and any number to the 1 power is the number itself. Using 1101 for another example, you have:
The first digit is 1, and 1 times 2^0 is 1.
The second digit is 0, and 0 times 2^1 is 0.
The third digit is 1, and 1 times 2^2 is 4.
The fourth digit is 1, and 1 times 2^3 is 8.
8 + 4 + 1 equals 13.
A function to perform the binary to decimal conversion is shown here.
Public Function BinaryToDecimal(BinaryValue As String) As Long
' Returns the decimal equivalent of a binary number.
Dim idx As Integer
Dim tmp As String
Dim result As Long
Dim digits As Integer
digits = Len(BinaryValue)
For idx = digits To 1 Step -1
tmp = Mid(BinaryValue, idx, 1)
If tmp = "1" Then result = result + 2 ^ (digits - idx)
Next
BinaryToDecimal = result
End Function
This function treats any character other than "1" in the binary value as a "0". You might want to add error-schecking code to ensure that the binary value contains only "0" and "1" characters.
Visual Basic Programming Tips 31
AutoRedraw and the Paint Event
Understanding the AutoRedraw property and the Paint event is important when you are using graphics in your Visual Basic programs. They are related to Visual Basic's graphics methods that let you create output on a form or a Picture Box control. These methods are Circle, Line, PaintPicture, Print, and PSet. These methods may not always work the way you expect. To see what I mean, create a new Standard EXE project and put the following line of code in the Click event procedure for the form:
Me.Circle (1500, 1500), 600
When you run the program and click the form, the circle appears just as you would expect. But try minimizing and then restoring the form - no circle (unless you click the form again)! You get the same result if you switch to another program that covers the form. What's going on - why is the circle temporary?
The answer lies in something called persistence. The form itself and any controls on it are persistent, which means that Visual Basic keeps a copy in memory and uses this copy to redraw the form on the screen when it has been hidden and then redisplayed. In contrast, the output of the graphics methods are not persistent - they are drawn to the screen only and are not saved in memory and cannot be redrawn when the form is hidden then redisplayed.
There are two ways to make the output of graphics methods persistent. One is to set the form's AutoRedraw property to True. This results in any output from the graphics methods being saved in memory as well as being drawn to the screen. They are now persistent and will remain visible when the form is hidden then redisplayed.
The other technique is to place the calls to the methods in the form's Paint event procedure. This event is triggered when the form is first displayed on screen and then again each time it needs to be redrawn after having been covered or minimized. To see this in action, move the line of code that calls the Circle method from the Click event procedure to the form's Paint event procedure. Now, the circle is displayed when the program starts and remains visible when the form is minimized and restored.
When AutoRedraw is set to True the form's Paint event procedure is never called. Which of these techniques you should use depends on the details of your program. Setting AutoRedraw to True usually simplifies programming, but it consumes extra memory and, particularly for complex graphics, can slowe down screen display.
Remember that the question of persistence applies only to the output of the graphics methods. Controls, bitmaps, and metafiles are always persistent.
Visual Basic Programming Tips 32
Getting Colors from a PictureBox Control
A Picture Box control can display various kinds of graphics images. Using the technique presented here, you can determine the color at any point in the image.
The Point method returns the color at a specified X,Y location in a Picture Box control. To get the color under the mouse pointer, put the code in the MouseMove event procedure for the control. Because the MouseMove procedure is passed the current X,Y coordinates of the pointer, this code is simple:
Private Sub Picture1_MouseMove(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
Dim rgb As Long
rgb = Picture1.Point(X, Y)
End Sub
The value returned by the point method is a type Long that encodes the RGB value for the color. To be useful, this encoded value must be separated into the individual R, G, and B components, each of which is an integer in the range 0-255 decimal or 00-FF in hexadecimal. Expressed in hexadecimal notation, the value returned by Point is:
00BBGGRR
Hexadecimal notation makes it easy to perform the extraction. If rgb is the value returned by the Point method, this extraction is performed as follows:
red = rgb Mod &H100
green = (rgb \ &H100) Mod &H100
blue = (rgb \ &H10000) Mod &H100
The final MouseMove event procedure is shown here. To see this in action, create a Standard EXE project and place a Picture Box and a Text Box on the form. Load an image into the Picture Box then run the project. You'll see that when the mouse moves over the image the Text Box displays the RGB values of the underlying pixel.
Private Sub Picture1_MouseMove(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
Dim rgb As Long
Dim r As String, g As String, b As String
rgb = Picture1.Point(X, Y)
r = CStr(rgb Mod &H100)
g = CStr((rgb \ &H100) Mod &H100)
b = CStr((rgb \ &H10000) Mod &H100)
Text1.Text = "R " & r & ", G " & g & ", B " & b
End Sub
You could place the same code in the MouseDown event procedure if you want to see the RGB values for only pixels that you click.
Visual Basic Tips
Sizing a Form's Interior
It's easy enough to set a form's size in code using its Width and Height properties, but this sets the form's outer size including borders and title bar. What if you want to set the form's interior area to a specific size, for example to precisely fit a Picture Box control? This tip shows you how.
You may think that you can change the form's ScaleWidth and ScaleHeight properties but this does not work. Setting these properties does not change the form interior's size but rather changes the units of measure used for graphics operations on the form. You can, however, read these properties to determine the size of the form's interior. This permits use of the following strategy:
Get the Form.Width value. This is the overall width of the form including borders.
Get the Form.ScaleWidth value. This is the width of the interior of the form.
Subtract the second value from the first. The result is the amount that the form's borders contribute to its overall width. Call this value deltaX.
Take the desired internal width and add deltaX to it. This is the overall width that the form will need in order to have the desired internal width.
Set the Form.Width property to the value obtained in step 4.
The same process is used for the height. To see this technique in action:
Start a new Standard EXE project.
Place a Picture Box control on the form and set its AutoSize property to True. This causes the Picture Box to automatically resize itself to fit the picture loaded into it.
Load an image into the Picture Box control.
Place the following code in the Form's Load event procedure.
Private Sub Form_Load()
Dim deltaX As Single, deltaY As Single
Picture1.Move 0, 0
deltaX = Me.Width - Me.ScaleWidth
deltaY = Me.Height - Me.ScaleHeight
Me.Height = Picture1.Height + deltaY
Me.Width = Picture1.Width + deltaX
End Sub
When you run the project you will see that the image is moved to the top left corner of the form and the form is resized to precisely fit the Picture Box. You can verify this by using the mouse to expand the form, revealing that the borders of the form and the Picture Box coincide exactly.
This technique requires that the ScaleMode, ScaleWidth, and ScaleHeight property of the form not be changed from the default values. This is because the size calculations depend on both the ScaleWidth, and ScaleHeight properties being in the default unit of twips.
Visual Basic Tips 33
Encoding and Decoding Passwords
It's not uncommon for programs to require some form of password protection. When a password is stored, in the registry for example, you do not want it in readable form because many users have the technical know-how to access the registry. Likewise, passwords are sometimes sent as part of a Web GET request. Some form of simple encryption should be used, as explained here.
There are various complex encryption alorithms available, but sometimes simple is better. A simple ASCII-value transposition is sometimes all that is needed. For example, suppose the password is "ajax7." You could encode it as follows:
1. The ASCII value of "a" is 97. Add 1 to get 98, which is the ASCII value of "b"
2. The ASCII value of "j" is 106. Add 1 to get 107, which is the ASCII value of "k"
3. The ASCII value of "a" is 97. Add 1 to get 98, which is the ASCII value of "b"
4. The ASCII value of "x" is 97. Add 1 to get 98, which is the ASCII value of "y"
5. The ASCII value of "7" is 55. Add 1 to get 56, which is the ASCII value of "8"
The encoded password is therefore "bkby8". The process of decoding is simply the reverse, subtracting 1 from each ASCII value. Visual Basic procedures for encoding and decoding passwords using this algorithm are shown here. It is assumed that passwords are limited to letters and numbers.
Public Function EncodePassword(pw As String) As St
گروه دور همی پارسی کدرز
https://t.me/joinchat/GxVRww3ykLynHFsdCvb7eg
https://t.me/joinchat/GxVRww3ykLynHFsdCvb7eg