How do I tell when an application executed using the SHELL command is finished?

For 32-bit VB: see Q129796 in Microsoft Knowledge Base.

For 16-bit VB: Shell() doesn't really return a task handle, it returns an instance handle. Any documentation that says otherwise is wrong. But never mind that; the answer to your question is to use the API call GetModuleUsage.

'Put this in the general declarations of your form/module
Declare Function GetModuleUsage Lib "Kernel" (ByVal hModule As Integer) As Integer

'Here's where you shell out to the other program
intHandle = Shell("PROGRAM.EXE")
Do While GetModuleUsage(intHandle) > 0

The FindWindow command can also be used (search the Tips help file for "How VB Can Determine if a Specific Windows Program Is Running"). I have had to use this when the program I shelled to unloaded itself and ran a different EXE. My program thought the shell was done (since the shelled EXE ended), but it really had just "moved on" to another EXE. Generally, the code in cases like this must be customized to fit the situation.

How do I access C style strings?

Use the 'lstrlen' and 'lstrcpy' calls found in the Kernel DLL.

How can I change the printer Windows uses in code without using the print common dialog? How can I change orientation?

You can change the printer the VB 3.0 Printer object is pointing to programmatically (without using the common dialogs). Just use the WriteProfileString API call and rewrite the [WINDOWS], DEVICE entry in the WIN.INI file! VB will instantly use the new printer, when the next Printer.Print command is issued. If you get the old printer string before you rewrite it (GetProfileString API call), you can set it back after using a specific printer. This technique is especially useful, when you want to use a FAX printer driver: Select the FAX driver, send your fax by printing to it and switch back to the normal default printer.

It is recommended (and polite, as we're multitasking) to send a WM_WININCHANGE (&H1A) to all windows to tell them of the change. Also, under some circumstances the printer object won't notice that you have changed the default printer unless you do this.

Declare Function SendMessage(ByVal hWnd As Integer,ByVal wMsg As Integer, ByVal wParam As Integer, lParam As Any) As Long
' Dummy means send to all top windows.
' Send name of changed section as lParam.
lRes = SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, ByVal "Windows")

Any tips for speeding up VB?

Who said "code in C"???? ;-)
  1. When SHOWing a form with lots of bound controls, have a blank frame covering everything. Then, in the Form_Activate event, set the Frame.Visible = False. This greatly speeds the display of the form and hides ugly thrashing as the data controls initialize.
  2. Try to keep any Global definitions to a minimum. Massive numbers of global variables really seem to slow VB Windows down (besides chewing up memory). In other words, if you've pasted a lot of stuff from the globals.txt file, trim all definitions and variables you don't use in your application.
  3. Keep the total number of controls and forms used to a minimum (you've probably already guessed that).
  4. Keep fancy graphics to a minimum (another one you know).
  5. Try "pre-processing" in the background (using DoEvents). Doesn't really speed anything up, but often there is a lot of "idle" time while the user is selecting menu's, buttons and such - if you can do some calculations, image loading or whatever during this idle time your user perceives the application is faster than it really is.
  6. Hide often-used forms rather than unloading them. Unloading saves memory, but it takes longer to re-load a form than to simply "un-hide" it.

You're limited by the system as to how quickly you can go from calling frmMyForm.Show to being able to type into the controls, but you can make the form appear to display faster. One technique is to keep forms loaded, and just switch their visibility on and off. This is heavy on resource usage, and doesn't help for the first time they're shown.

Most forms have some processing (eg. querying a table to fill a list box) that goes on when they're first opened, and this is what causes the most serious delay. It's possible to display the form, make its controls appear on screen, then do the slow processing before finally making the form "live". As the user can see things happening, the perceived delay is less obvious.

Include the following code in your form

Show the form by using:

Screen.MousePointer = HOURGLASS
frmMyForm.Show MODAL
  • Polling a control for its properties directly is 10 to 20 times slower then placing the property values you need into variables and testing the value of the variables.
  • Swap tuning: Modules are not loaded until used; put related code in the same modules, reduce the number of intermodule calls and keep modules small.
  • Binary file I/O is faster then Text/Random.
There was also a lot of discussion about "apparent" speed i.e: how it looks on the screen as opposed to how fast it's chugging internally. It was noted that the cute little flashing menu items and exploding windows in the Mac amounted to a little razzle-dazzle to distract you from how long it took to actually load something and get it on the screen. Keeping all your forms loaded but hidden until needed was suggested. Also the use of progress indicators and a simple quickly loaded and drawn startup form. Also preloading data you expect to need.

How do I speed up control property access?

Instead of using a property in a loop, you will be better off using a normal variable in the loop and then assign the variable once to the property afterwards. Also, when reading a property, you should read it once into a variable instead of using it in a loop.

Sometimes it is not possible to simply put contents of a property into a variable. For example, if you are using a list box or you need to conserve memory. In these cases you can send the WM_SetRedraw message to the control to prevent redrawing. You can typically increase the speed 6-10 times - or even more.

'Add the following declares:
Declare Function SendMessage Lib "User" (ByVal hWnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, lParam As Any) As Long
Const WM_SetRedraw = &HB
'Add this to your code:
Result% = SendMessage(Text1.hWnd, WM_SetRedraw, 0, 0) 'redraw off
'Do your stuff here!
Result% = SendMessage(Text1.hWnd, WM_SetRedraw, 1, 0) 'redraw on
This same method applies to list boxes and other controls.

How much gain in performance will I get if I write my number crunching routines in C instead of Visual Basic?

Probably the best solution to the number crunching problem is to write the number crunching routines as a custom control or a DLL, and plug it into a VB app. VB interface handling is not significantly slower than, say C++, and most of the wait is associated with Windows.

Some real world experience speaks volumes about this one:

I wrote some time consuming code in VB to solve a combinatorical (does this word exist in English?) problem. The code consists of one main recursive function, which calls itself very often. It took a night to compute a certain problem. I was rather disappointed and then decided to write the central routine in C++. It was a 1:1 transcription. The routine was compiled with the MS C++-Compiler. It took only 22 Minutes for the same problem. Amazing, isn't it? The routine doesn't do any floating point arithmetic, only integer, and handles some arrays. The PC was a 33MHz 486. And the second amazing thing is, that a IBM RS6000 (560)-Risc-machine needed 17 Min for the same code. I was the only one on the machine. I thought it should be much faster. The MS C++ seems to make very fast, optimized code. The optimization was configured to make fast code.

How do you make a TEXTBOX read only? Or, how do I prevent the user from changing the text in a TEXTBOX?

Visual Basic 5.0 provides a .locked property. Setting it to True will make the TextBox read-only.

Earlier, there's was a lot of ideas on this one. You can grab the _KeyPress and _KeyDown events and set them to zero. However, the best idea is to use the Windows API SendMessage function to tell the control to become read-only (16-bit code follows):

'After making the following declarations...
Global Const WM_USER = &H400
Global Const EM_SETREADONLY = (WM_USER + 31)

Declare Function SendMessage Lib "User" (ByVal hWnd As Integer ByVal wMsg As Integer, ByVal wParam As Integer, lParam As Any) As Long

'Then Try:
SendMessage(Text1.hWnd, EM_SETREADONLY, 1, 0)

This will still allow the user to copy from the text box. If you need to disable this (why?), steal the Ctrl-C in the _KeyPress event.

How can I create a OCX/VBX?

VB 4.0 supports some OCX creation. VB 5.0 has full support for creating OCXes including ActiveX. However, they are not standalone.

Applies to VB 3.0: VBXs (Visual Basic eXtensions) are practically always written is C (Borland C++, but mainly MS VC++). You should refer to the _Control Development Guide_ (in VB Professional Features Vol. I) and any relevant documentation for your compiler. Followup questions should normally be directed to* or comp.lang.c*. There are some example VBX's with C code supplied with VB3 Pro. You'll find them under the directory [VB]\CDK.

How do you change the system menu (on the Control-Menu Box)?

You can turn off the minimize and maximize menu options by changing properties, but what if you need to remove the "close" option?
'Make the following declares.

Declare Function GetSystemMenu Lib "User" (ByVal hWnd As Integer, ByVal bRevert As Integer) As Integer
Declare Function RemoveMenu Lib "User" (ByVal hMenu As Integer, ByVal nPosition As Integer, ByVal wFlags As Integer) As Integer

Global Const MF_BYPOSITION=&H400

'Use the following code to remove the "close" option.
SystemMenu% = GetSystemMenu (hWnd, 0)
Res% = RemoveMenu(SystemMenu%,6, MF_BYPOSITION)
'(also remove the separator line)
Res% = RemoveMenu(SystemMenu%,6, MF_BYPOSITION)

Adding menu items to the control menu is more complicated, since you need to respond to the events triggered when the user selects the new options. The Message Blaster (msgblast.vbx, see details in beginning of FAQ about how to get files) contains example code.

How do I play MID, WAV or other multimedia files?

Use the MSMCI.VBX/OCX, provided with VB/Win Pro. You can also declare and call the MM-functions manually:
Declare Function mciExecute Lib "MMSystem" (ByVal FileName as String) As Integer
Sub Form1_Click ()
iResult = mciExecute("Play c:\windows\mkmyday.wav")
End Sub

Playing a WAV file is covered in the VB Tips help file (there is a Windows call that is for this specificially; see below). The routine won't play MIDI files or other sound formats, however.

Declare Function sndPlaySound Lib "MMSYSTEM.DLL" (ByVal WavFile$, ByVal Flags%) As Integer
Global Const SND_SYNC = &H0
Global Const SND_ASYNC = &H1
Global Const SND_NODEFAULT = &H2
Global Const SND_LOOP = &H8
Global Const SND_NOSTOP = &H10

How can I call a 'hidden' DOS program from VB?

Applies to Windows 3.x: If you run a DOS program minimized using the SHELL command, it will never complete. This is because DOS tasks by default are NOT setup to run in the background. The easiest way to get around this is to make a PIF file for the program you need to run with the "Background" option checked. Then SHELL to the PIF file to run the DOS program and it will return control to your VB application when it terminates.

Another possibility is to use the Shell-command not directly on your DOS-executable but to shell COMMAND.COM with the parameter "/C" to make the DOS-Box automatically close when the DOS-application has ended, for example (the /c parameter tells the Command interpreter to execute the following command line):

taskID = Shell(Environ$("Comspec") & " /c dosexe.exe", 6)
With VB4 (and above) you can use "vbHide" (= 0) as a parameter to make the minimized DOS-Window neither appear in the taskbar nor in the tasklist, for example:
taskID = Shell(Environ$("Comspec") & " /c dosexe.exe", vbHide)
Tip: If you edit or replace the _DEFAULT.PIF file in the Windows directory to allow execution in background, this will apply to all DOS boxes that is not run with it's own .pif! This applies also to Windows 95 and NT 4.0. Note that Explorer hides the extensions of .pif files no matter what; they are now called shortcuts.

How do I do drag & drop between applications?

Applies to VB 3.0 with Windows 3.x: MSGBLAST.ZIP (the famous Message Blaster by Ed Staffin and Kyle Marsh) available on various web sites, tell you everything you want to know about this and other advanced stuff. This is now (inexpensive) shareware, but the older freeware version is still supposed to be available. Get the file mentioned above for more info.

Short glossary for the confused ones :-)

  • Drag & Drop Client: the form you drop objects to/on
  • Drag & Drop Server: the form you drag object(s) from

How do I use GetPrivateProfileString to read from INI files?

Windows 95: use GetSetting and SaveSetting which accesses the registry entries under "VB and VBA applications" directly. If you want to manipulate any and all registry entries, VB provides no direct method and the code needed is surprisingly complex. Also, bear in mind that a small error in writing to the registry may blow up your entire 95 or NT and require a reinstall!

Applies to Windows 3.x: There's a good example of accessing *.INI files in the Knowledge Base, but here's the basic idea:

'You declare these API function as usual:
Declare Function GetPrivateProfileString Lib "Kernel" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Integer,ByVal lpFileName As String) As Integer

'Then in your code you do like below:
strIniFile = "WIN.INI"
strSection = "MyProgram"
strKey = "Language"
strDefault = "English"
iLength = 70

strReturn = String$(iLength, " ") 'Pad the string first!
iResult = GetPrivateProfileString(strSection, strKey, strDefault, strReturn, iLength, strIniFile)

WARNING: Be aware that there was an ERROR in the Windows 3.1 API documentation that came with VB 3.0. Here's the scoop:

Knowledge Base article Q110826 (DOCERR: GetPrivateProfileString Declaration Incorrect in API) corrects a documentation error for the GetPrivateProfileString function call as described in the Windows version 3.1 API Reference help file that shipped with Microsoft Visual Basic version 3.0 for Windows. The CORRECT declaration is as follows:

Declare Function GetPrivateProfileString Lib "Kernel"(ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Integer,ByVal lpFileName As String) As Integer
Note that the "ByVal" keyword was omitted from the second parameter in the online reference. This means that the function is passing the second parameter (lpKeyName) by reference. It needs to be passed by value.

The most common problem that occurs when using the incorrect declaration is that when the function is called, it returns a copy of "lpdefault" in the "lpReturnedString" parameter instead of the actual value referenced by KeyName.

The correct declaration is as follows:

Declare Function WritePrivateProfileString Lib "Kernel" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lplFileName As String) As Integer
(all on one line of course!)

How do I implement Undo?

A multi-level undo is implemented in Windows 95 controls, and a normal undo in Windows 3.x. Your user can press Ctrl-Z for undo.

If you can't use these, you will have to keep track of changes yourself. There's no magic involved, just some coding.

To use the standard Text box or Combo box undo functions:

'Do the following declares:
Declare Function SendMessage Lib "User" (ByVal hWnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, lParam As Any) As Long
Global Const WM_USER = &h400
Global Const EM_UNDO = WM_USER + 23

'And in your Undo Sub do the following:
UndoResult = SendMessage(myControl.hWnd, EM_UNDO, 0, 0)
'UndoResult = -1 indicates an error.

How do I create a window with a small title bar as in a floating toolbar?

Applies to Windows 3.x: Download the MSGBLAST VBX from (filename or (better) from or mirrors. The example files provide an example of a form with a small title. When you see it, you'll understand why I haven't included a full explanation here!

I hear that VB makes pseudocode. What is is Pseudocode?

VB/Win does not (before VB 5.0) generate machine code like most compilers do. Instead it creates what is called pseudocode (a real misnomer, IMO). A good explanation is given below:

A bit of history: the original P-code was an instruction set for a "virtual Pascal" machine. This came with a portable Pascal compiler written at ETH in Zuerich. The portable compiler produced instructions for this phony machine which had an instruction set ideally suited to the stack and heap management of Pascal. To execute portable Pascal programs, you had two choices: either write an interpreter for P-code, or translate the small set of P-code instructions (there were about 80) into assembler; assemble it; and run it at native speed. Thus "P-code" originally stood for "Portable" or "Pascal" code. The broader meaning, "pseudo-code" came later. P-code was widely popularized by the UCSD Pascal system, a small workstation that was implemented entirely in Pcode and interpreted. It was sold for some years, and one company even re-did the microcode for a PDP-11 microchip to interpret P-code. The original Borland Turbo Pascal had obvious similarities to the UCSD system although it was not interpreted. The dialect was virtually identical. Today P-code is used extensively in Microsoft apps, for two reasons. First, it is much more compact than native code; so the apps are smaller. Second, having an interpreter at the core of an app makes it much easier to customize and extend. That is why VB is becoming the heart of the MS major apps. It is simply not true that P-code apps run much slower than native apps. The slowdown is determined by the granularity of the interpreted routines. If every little thing is an interpreted op, the slowdown might be as much as 3-to-1 for the 80x86 architecture, or about 2-to-1 for the Motorola 68000 family (which is better suited to writing interpreters). But in practice, modern P-code systems have large-scale instructions, each of which is executed by a big compiled subroutine. These subs run at native speed, so the overhead of the interpreter is occasional at worst.

It is also possible that since the code may not need recompilation to run on other platforms if the run-time interpreter is first ported, VB applications can become very portable. This depends on Microsoft's long-term plans. (The previous sentences were written before rumours of VB being ported to the Alpha processor came out. Ironically, at the same time VB is being made a true compiler with VB 5.0)

A note on the word "pseudocode": I wrote above that it is a misnomer, and I stand on that. Pseudocode is really the pascal-like (mostly) explanation of an algorithm that is intended for human readers, not computers. But since somehow the term pseudocode stuck to the psaudo-machine-code created by VB the word is used here.

Does VB support pointers to functions?

Yes. VB 5.0 adds callbacks and pointers. See AddressOf operator in docs/help.

For earlier versions, this answer applies:

No, it does not.

How do you change the icon and otherwise manipulate the DOS box?

Enclosed is the results of my digging around to enable me to change the icon of a DOS box launched with the SHELL function. It illustrates most aspects of dealing with DOS boxes. Example of launching PIF file minimized and running it in the background (Needs Execute Background box checked in PIF file), and assigning the PIF file a new Icon rather than the default DOS Box Icon. (Note: it seems this method changes the icon of ALL the active DOS boxes)

How do I make the mouse cursor invisible/visible?

Use the API call ShowCursor(False) or ShowCursor(True). Just be aware that the Windows cursor is a shared object, so if your process hides it then it must also redisplay it as well. Also the function is not truly "True" or "False" in nature - it is LIFO, so that if your process has for some reason set it "False" multiple times then you must set it "True" the same number of times in order to re-display the cursor. Hard to explain but play with it - you'll see what I mean...

How do I create controls dynamically (at run-time)?

Search of VBKB_FT on "control near array" finds a number of articles, including Article ID: Q79029 "Creating Nested Control Arrays in Visual Basic" This is the article to read to understand control arrays (plus a number of other neat concepts). In particular, look at the procedure "loadall_click".

The key is that you need to create the first instance of your_control (ie. your_control(0)) at design time.

How do I set the Windows wallpaper at runtime?

I'm surprised this isn't in the FAQ yet. [ED: now it is!] You need the SystemParametersInfo API function and the SPI_SETDESKWALLPAPER constant.

For this you will need the following constants and function declaration...

Declare Function SystemParametersInfo Lib "User" (ByVal uAction As Integer, ByVal uParam As Integer, lpvParam As Any, ByVal fuWinIni As Integer) As Integer
You then call the function as follows...
Result% = SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, ByVal BMPFile$, SPIFlags%)
... where SPIFlags% is 0 if the change is not to be made permanent, or '(SPIF_UPDATEINIFILE OR SPIF_SENDWININICHANGE)' if it is to be carried across into future Windows sessions.

Note: Please be certain to include the ByVal keyword before the bitmap filename, as this argument is declared as Any, not as ByVal String.

How do I call Windows Help files from a VB program?

If you set HelpContextID for a control, pressing the F1 key will open this topic in the help file you have associated with your project. (App.Helpfile). I.e.
will open the associated help file and display topic "21004" (see the chapter on help file development in documentation to see how you create your help files).

In Visual Basic 5.0, the common dialog control supports help access directly.

Private Sub TextBox1_Click()
  CommonDialog1.HelpFile = "VB5.HLP"
  CommonDialog1.HelpCommand = cdlHelpContents
End Sub
The old method if setting CommonDialog1.Action=6 is still supported for backwards compatibility, and is essentially equivalent.

How do I shut down or reboot Windows from my program?

In 32-bit VB use the following:

Declare the API-function

Declare Function ExitWindowsEx Lib "user32" (ByVal uFlags As Long, _
ByVal dwReserved As Long) As Boolean
Public Const EWX_SHUTDOWN = 1
and use
success = ExitWindowsEx(EWX_SHUTDOWN, 0)
If successful, the function returns true. You can force the shutdown by using EWX_FORCE = 4. To make Windows 95 reboot use EWX_REBOOT = 2. To only log off use EWX_LOGOFF = 0.

With 16-bit VB, you do:

Declare Function ExitWindows Lib "user" (ByVal wReturnCode as Long, _
ByVal dwReserved as Integer) as Integer
To exit Windows:
RetVal% = ExitWindows(0, 0)
To exit and restart Windows:
RetVal% = ExitWindows(&H42, 0)
To exit Windows and restart the system:
RetVal% = ExitWindows(&H43, 0)
If any application refuses to terminate, zero will be returned. If succesful, there will be no return (of course).

Note that SendKeys to close the Program Manager (Win 3.x) will never work because it shows a sys-modal dialog that will stop all applications until being answered (therefore SendKeys cannot send the "OK"-press because the "sending" application is stopped).

How do I launch a file in its associated program?

The Shell statement unfortunately only supports launching an EXE file directly. If you want to be able to launch, e.g. Microsoft Word by calling a .DOC file only, you can make your VB application launch the associated program with the document using the following method:

Do the following declares:

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

Private Declare Function GetDesktopWindow Lib "user32" () As Long

Declare Function ShellExecute Lib "SHELL" (ByVal hwnd%, _
ByVal lpszOp$, ByVal lpszFile$, ByVal lpszParams$, _
ByVal lpszDir$, ByVal fsShowCmd%) As Integer

Declare Function GetDesktopWindow Lib "USER" () As Integer

Private Const SW_SHOWNORMAL = 1

Launch applications using associated extensions with the following code:
Function StartDoc(DocName As String) As Long
  Dim Scr_hDC As Long
  Scr_hDC = GetDesktopWindow()
  StartDoc = ShellExecute(Scr_hDC, "Open", DocName, "", "C:\", SW_SHOWNORMAL)
End Function

Private Sub Form_Click()
  Dim r As Long
  r = StartDoc("c:\my documents\word\myletter.doc")
  Debug.Print "Return code from Startdoc: "; r
End Sub

How do I open the web browser and make it show a web page?

Make the following declares in 32-bit VB (16-bit VB only needs a small change in API declare; see topic above):
Private Declare Function ShellExecute Lib "shell32.dll" Alias _
"ShellExecuteA" (ByVal hwnd As Long, ByVal lpOperation As _
String, ByVal lpFile As String, ByVal lpParameters As String, _
ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
And create a button (or whatever) and call the default web browser using this method:
Private Sub cmdGotoHomesite_Click()
  Dim ret&
  ret& = ShellExecute(Me.hwnd, "Open", "", "", App.Path, 1)
End Sub
You can actually use exactly the same method to open your default email client; simply substitite the http URL with a "" in the example above.

How can I start a remote access dial-up connection from VB (one of those stored in Remote Access folder)?

To initiate a Dial-Up Networking connection, shell the following commandline:
rundll rnaui.dll,RnaDial <connection>
(Note: all components of this command line are case sensitive, and the location of spaces and commas is critical!)

For example, to start a connection named "My Connection", use the following function:

' First, bring up the "Connect to"-window
taskID = Shell("rundll.exe rnaui.dll,RnaDial " & "My Connection", 1)
DoEvents ' Wait until RAS-window is shown
SendKeys "{ENTER}", True ' Press "Return" = "Connect"
The available connection-names for the current user are stored in the registry under HKEY_CURRENT_USER\RemoteAccess\Addresses.

Another, much mor difficult question is how to check for the presence of an active dial-up session. If you have a good solution that works for all flavors of Windows RAS including NT, please tell me. One way that works so-so for Windows 95 is to cycle through the children of the desktop and look for one which classname is "#32770". If it is, check wether its caption is either "Connected to " & NameOfDUN or only NameOfDUN (in case the window got minimized, the caption changes for that case).

For more advanced RAS access, see you should use the API RAS functions. See this KB article: Q150948 SAMPLE: Using Win32 RAS Functions from Visual Basic 4.0 32-bit.

How do I add an icon to the icon tray under 32-bit Windows?

The Windows95 explorer shell provides a tray icon area on the taskbar (usually lower right corner of screen).

There are a number of 3rd party OCX files available in various toolboxes, some freeware. You may consider using these instead, since there may be some unwanted side effects with the simple VB-only solution. Look around.