                                   VB Tips

                      Compiled by Nelson Ford, 71355,470
                  For distribution on the MSLANG Forum only.

This is a compilation of information about VB that has flowed through the
Forum here (plus my own input). The hope is that new users can refer to this
file rather than having to ask the same questions on the forum all the time.
Experienced users whose memory is bad as mine might benefit from this as well.

Main contributors are:
Jonathan Zuck
Keith Funk
Mark Novisoff
Ted Young
Dennis Harrington
the Microsoft Section Leaders

Note: There are a number of very valuable free DLL's and VB routines on DL1
("New Uploads") and DL6 ("VB"). It is highly recommended that you Browse these
two DL's. A few are mentioned in this file, but many more are available.

Uploading: If you would like to share your coding with others, upload to DL1,
not DL6. All new uploads go to DL1 for 30 days and then are moved to the
appropriate DL (6, for VB). Do NOT put VBRUN100.DLL in your archive, nor any
of the other DLLs that are available on here. All that does is increase the
download time for everyone who already has those files. Instead, in your
upload description, just tell people that they need to download those files
too, if they don't already have them.


CONTENTS:  [Names in brackets are files in the MSLANG DL's 1 or 6.]

General:
  Button Colors
  .EXE Size
  Far Pointers
  Help Files
  Icon - Get Rid Of
  Networks & VB
  Saving Your Work
  Screen type
  Sound.DLL (Need TPW)
  Type...End Type

Arrays:
  Dynamic Arrays
  [VBSORT]

Combo Boxes:
  Changing Text
  Color
  Pseudo Combo Dropdown List Box

DOS Functions:
  File Copying
  [VBDOS]

Form & Control Placement, Sizing:
  Controlling Form Size
  "Floating" Window
  Form Size and Granularity
  Grouped Controls
  Mouse Pointer, Position
  Placing Forms, Controls

File I/O:
  Btrieve
  Deleting Data in Sequential File
  MKI$ & CVI in VB

Fonts:
  Using Different Fonts, Colors in a Box
  System Font

Forms: (Also see "Form & Control Placement, Sizing", above)
  Focus & Order of Execution
    Focus on StartUp
  SetFocus

Keyboard & Mouse:
  Trapping Double-Click before Click
  Using "Enter" in Place of "Tab"

List Boxes:
  Finding an Item Added to a Sorted List Box
  Inhibiting Screen Updating
  Linking Sorted and Unsorted List Boxes
  Searching For An Item
  [VBSORT]

Picture Boxes:
  Copying a Drawing to Clipboard
  Drawing - Scale
  Saving Picture Files

Printer:
  Printer Setup
  Printer Control Codes
  Printing Forms

System:
  Calling the Windows 3 Calculator
  hWnd for a Control
  .INI Files
  Multi-Instance App Prevention
  SendMessage
  Windows Termination

Text Boxes:
  Cursor (text), Position
  Data Entry Masking
  Data Entry Routine
  Data Entry Text Limit
  Flicker
  Flashing Text

Timer
  Using the Timer






Button Colors:

Button colors are controlled by WIN.INI.  See the 6/11/91 issue of PC Mag
for details. Of course, changes you make there apply globally.
---------------


.EXE Size:

1. Long variable names increase EXE size.
2. Comments add a couple of bytes per line to EXE size.
3. A Global DIM of an array adds to EXE size.
   For example:
     Global sample as string*20000
   will add about 20k to the size of the EXE file, but the same DIM
   in a Form will not.
---------------



Far Pointers:

Here's a question regarding direct calls to the Windows API from a Visual Basic
procedure. In trying to set up a call to the API entry "Polygon", I discovered
that one of the arguments is a far pointer to an array of structures. I've
searched the VB documentation, but can't find a method that will return the
run-time address of a variable (or an array element) as a far pointer.

Is there a VB technique for passing a far pointer as an argument -- if so, how?
Also, how would such an argument be specified in the corresponding DECLARE
statement? Many thanks to anyone who can supply this information.

(Fm: Mark Novisoff (TA) 73047,3706)

If the structures themselves don't require pointers (which is the case with
Polygon), then it's a piece of cake. Use TYPE...END TYPE to declare a model
structure, and then DIM an array of the structures.

In your Global module Declare statement, use the "As" syntax:

   Type Points
     ' Define the structure
     X As Integer
     Y As Integer
   End Type
   Declare Function Polygon Lib "Gdi" (hDC%, MyStruc As Points, nCount%)

In your code:

   ReDim MyStrucArray(0 To 10) As Points
   ' Set the variables in the array here
   Result = Polygon(hDC%, MyStrucArray(0), nCount%)

Note that this results in passing a far pointer to the zeroth element of the
array. Because the length of the structure is known to the DLL routine, it
will figure out where the rest of the elements are.
---------------


Help Files:

1. You can create Help files more easily and cheaply than with the SDK: use
   the help compiler (HC.EXE) that comes with Turbo Pascal for Windows and
   Borland C++.

2. A shareware program named Xantippe is a good front-end for making help
   files.
---------------


Icon - Get Rid Of:

1. Click on the form
2. Select the Icon property from the properties bar.
3. Click on the middle portion of the properties bar, where (icon) shows.
4. Press the Del key
---------------


Networks & VB:

VBRUN100.DLL and VB apps (EXE's) should be placed on each machine's hard disk
to avoid significant performance degradation.
---------------


Saving Your Work:

Several users have reported losing their work in different ways. If you always
save before test-running your code, you can prevent most losses. If you get
any kind of warnings or other indications that something might be wrong with
your system, try to get into DOS (or use File Manager) and copy your project
files to a backup directory. Many people have had their project files
"trashed" when the system went screwy. There is a utility on the DLs here for
copying all the files in a Project - very handy to have, although a better
idea, generally, is to keep each project in its own subdirectory.
---------------


Screen type:

z = Screen.Height
If z = 6000 Then
  Type$="CGA"
ElseIf z = 7000 Then
  Type$ = "EGA"
ElseIf z > 7000 Then
  Type$ = "VGA or Better"
End if

There's another way to do this, calling GetDeviceCaps to find out the vertical
resolution; but this method is a lot easier... BTW, if you want to know if it
is exactly VGA, not "or better" (i.e., better than 640x480), the number for
that 7200 if memory serves...
---------------


Sound.DLL (Need TPW):

Here's my DLL written in TPW. I had no documentation besides the Windows
Programmer's Reference so maybe someone here can tell me if I'm on the right
track. Some questions come to mind such as: how large shoule I make the voice
queue? Is it unecessary to open and close the sound device every time I want to
set a note?

library SoundDLL;

uses WinTypes, WinProcs;

procedure PlayNote(Note, nLength, Cdots: Integer);export; begin
    OpenSound;
    SetVoiceAccent(1,100,255,S_NORMAL,0);
    SetVoiceQueueSize(1,1000);
    SetVoiceNote(1,Note,nLength,Cdots);
    StartSound;
    WaitSoundState(S_QueueEmpty);
    CloseSound;
    StopSound; end;

exports PlayNote index 1;

begin end.

The declaration in VB (general):

Declare Sub PlayNote Lib "e:\tpw\output\soundll.dll" (ByVal note%, ByVal
Length%, ByVal Cdots%)

(Mark N.):
The size of the voice queue is one of those numbers that you simply "Pull
out of thin air". It depends on what you're going to do. For example, in
VBTools, we set the queue to 8K because we include several large pieces of
music.

OTOH, if you're going to play single notes on an occasional basis, then 1K
should be plenty.

It is not necessary to open and close the sound device every time. In fact,
if you close it while there are notes in the queue, they'll be lost!
I suggest that you do what we've done in VBTools:

1. Open the sound device when the user first calls your proc.
2. If the device is open, then close it when your DLL unloads.
3. Give the user a method to close it so that sound can be generated by
   other apps.
---------------


Type...End Type:

Be sure when using arrays with Type variables to put the variable number
AFTER the type name and BEFORE the element name.
Example:
Type Test
  a1 as string*1
  a2 as string*2
End Type

Dim TypeName as Test
TypeName(i).a1 = "xyz"  NOT  TypeName.a1(i) = "xyz"
---------------


Dynamic Arrays:

In order to use a dynamic array, you must REDIM it in a module or form:
(Mark N.)

Global:
  Type SomeType
       X As Integer
       etc
  End Type
  Global ArrayName() As SomeType

Module or Form:
  Redim ArrayName(x) As SomeType    ' x can be a number or a variable
---------------


Combo Boxes: Changing Text

Text is read-only, but you can accomplish the same thing with:
Combo1.ListIndex = 3. This would select and display the 4th item in the list.
---------------


Combo Boxes: Color

BackColor does not apply to the list portion of the combo box, only the 'edit'
part.
---------------


Pseudo Combo Dropdown List Box:

To implement a pseudo Combo DropDown List Box like the one used in the VB
Help/Index/Search Window: As you type text in a Text Box, VB hilights the
first entry in the List Box that matches what you have typed. This can be
implemented in VB by using a Text Box, a List Box and the API message
LB_FINDSTRING.
---------------


File Copying:

Open "FileIn" for Binary as #1
whole = Lof(1) \ 32000        'numer of whole 32768 byte chunks
part = Lof(1) MOD 32000     'remaining bytes at end of file
buffer$ = string$(32000,0)
start& = 1
Open "FileOut" for Binary as #2
for x=1 to whole                     'this for-next loop will copy 32,000
       get #1, start&, buffer$       'byte chunks at a time. If there is
       Put #2, start&, buffer$       'less than 32,000 bytes in the file,
       start&= start& + 32000        'whole = 0  and the loop is bypassed.
next x
buffer$ = string$(part, 0)           'this part of the routine will copy
get #1, start&, buffer$              'the remaining bytes at the end of the
put #2, start&, buffer$              'file.
close
---------------


Controlling Form Size:

Set MaxButton to False so the user can't maximize the form.

General_Declarations:
Dim OldWidth As Single                       '-- width before resizing.
Dim OldHeight As Single                      '-- height before resizing.
Const MinWidth = 6000!, MaxWidth = 9000!     '-- change these values to...
Const MinHeight = 3000!, MaxHeight = 6000!   '-- the ones you want.

Sub Form_Load ()
   OldWidth = Width
   OldHeight = Height
End Sub

Sub Form_Resize ()
  If WindowState <> 1 then        '-- allows user to minimize window.
    If Width < MinWidth Or Width > MaxWidth Then
      Width = OldWidth
    Else
      OldWidth = Width
    End If
    If Height < MinHeight Or Height > MaxHeight Then
      Height = OldHeight
    Else
      OldHeight = Height
    End If
  End If
End Sub
---------------


"Floating" Window:

Sometimes you may want a small window to show above the current window, but VB
does not offer this feature. Here is how to force it (courtesy of Ted Young):

Global.Bas:
Declare Sub SetWindowPos Lib "User" (ByVal hWnd As Integer,
                                     ByVal hWndInsertAfter as Integer,
                                     ByVal X as Integer,
          (put this all on  -->      ByVal Y as Integer,
           one line)                 ByVal cx as Integer,
                                     ByVal cy as Integer,
                                     ByVal wFlags as Integer)
Declare Function GetWindow Lib "User" (ByVal hWnd as Integer,
                                       ByVal wCmd as Integer) As Integer
'  Set WindowPos Flags:
Global Const SWP_Nosize = &H1
Global Const SWP_NoMove = &H2
Global Const SWP_NoActivate = &H10
Global Const SWP_ShowWindow = &H40

Form1, Load:
  Form2.Show  ' this is the "Floating" window
End Sub

Form1, Timer1  (set Interval to 50 in Properties)
Sub Timer1_Timer
  If GetWindow(Form2.hwnd,0) <> Form2.hWnd Then
    wFlags = SWP_Nomove or Swp_Nosize or Swp_ShowWindow or Swp_NoActivate
    SetWindowPos Form2.hWnd, 0, 0, 0, 0, 0, wFlags
  End If
End Sub
---------------


Form Size and Granularity

Check the "Granularity" under the Desktop settings in the Control Panel. If
this number is anything but zero, you'll get the effect of all windows only
being able to be sized and placed by increments of 16 pixels multiplied by the
Granularity number. Set it to zero and this should fix things.
---------------


Grouped Controls:

If you want to group a set of controls within another control, such as a
Frame, you cannot do so by double-clicking the control and moving it into the
Frame. You must single-click the control and then click-and-drag INSIDE the
frame (or other control) to draw the added control.
---------------


Mouse Pointer, Position:

Sometimes it nice to be able to place the user's mouse cursor for him. One
example is when the user has to click on a button to continue, you can put the
cursor on the box for him.

Declare Sub SetCursorPos Lib "User" (ByVal x%, ByVal y%)
example:  Call SetCursorPos(100,200)

The above code will do a rudimentary job of positioning the cursor.
Here is some more information about positioning:

Using the Windows API call SetCursorPos, you can move the mouse to any
location on the screen. Note that the x & y coordinates are *screen*
coordinates.  You'll need to translate the coordinates in your VB
program to screen coordinates.  One way is to get the current coordinates of
the mouse using GetCursorPos, and then moving the mouse in relation to those
coordinates.  Note that GetCursorPos returns the coordinates in a Type
structure (see below).  You can also use the API functions ClientToScreen
and ScreenToClient to convert the coordinates from the Client (your Form) to
the Screen, and back.  Experiment with the functions to see which suits your
purpose, and holler if you have any questions.  Also, I'd recommend
downloading WINAPI.ZIP which has all the Declares necessary for calling the
API functions.

 Declare Sub SetCursorPos Lib "User" (byval x as integer, byval y as integer)
 Declare Sub GetCursorPos Lib "User" (lpPoint as PointAPI)
 Declare Sub ClientToScreen Lib "User" (ByVal hWnd As Integer,
                                        lpPoint As PointAPI)
 Declare Sub ScreenToClient Lib "User" (ByVal hWnd As Integer,
                                        lpPoint As PointAPI)
 Type PointAPI
      x As Integer
      y As Integer
 End Type

Note: Type the declares on one line, not two as shown above.
---------------


Placing Forms, Controls:

Sub InitializeWindow
   Height = Screen.Height * .75
   Width = Screen.Width * .75
   Top = (Screen.Height - Height) / 2
   Left = (Screen.Width - Width) / 2 '
*** Then place and size your controls.
End Sub

If you want to make the form a specific size, then Scale it in inches,
centimeters, points, or twips (a twip is 1/20th of a point), and don't bother
making references to Screen.Height or .Width.
---------------


Btrieve:

There are the DLL-Declarations for Btrieve. The requestor must be loaded
before Windows:

Declare Function wbtrvinit Lib "wbtrcall.dll" (ByVal init$) As Integer
Declare Sub wbtrvstop Lib "wbtrcall.dll" ()
Declare Function btrcall Lib "wbtrcall.dll" (ByVal a%, ByVal b$, ByVal c$,
  d%, ByVal e$, ByVal f%, ByVal g%) As Integer

FIELD and VARPTR are definitely not necessary. You'll need to set up a
user-defined type to work with Btrieve (instead of FIELD). The call syntax for
Win Btrieve takes care of pointing to the record (instead of VARPTR). Just be
sure to pass any variable-length strings (i.e, A$) BYVAL.
---------------


Deleting Data in Sequential File:

Here's an idea for deleting text in the middle of a sequential file that might
work.  Haven't tried it, but it seems like it would work and wouldn't be
difficult to implement. First, you'll have to save file pointers to the
beginning of each message (e.g., in a Long Int array during message load using
the SEEK function) in MsgPtr&(). Now, if you want to delete message 50 and
there are a total of 200, you'd do the following:

 MsgToDelete = 50
 TotMsgs = 200
 BlockSize& = 32768&'-- make this larger or smaller to find optimum size
 Temp$ = Space$(BlockSize&)
 Open "MESSAGE.MSG" For Binary As #1

 SizeOfFile& = LOF(1)
 SizeToMove& = SizeOfFile& - MsgPtr&(MsgToDelete + 1)
 NumBlocks = SizeToMove \ BlockSize&
 LeftOver = SizeToMove& - Num32KBlocks * BlockSize&
 FromLoc& = MsgPtr&(MsgToDelete + 1)
 ToLoc& = MsgPtr&(MsgToDelete)

 For i = 1 To NumBlocks
     Seek #1, FromLoc& + BlockSize& * (i - 1)
     Get #1, Temp$
     Seek #1, ToLoc& + BlockSize& * (i - 1)
     Put #1, Temp$
 Next
 Temp$ = Space$(LeftOver)
 Seek #1, FromLoc& + BlockSize& * NumBlocks
 Get #1, Temp$
 Seek #1, ToLoc& + BlockSize& * NumBlocks
 Put #1, Temp$
 '-- Now adjust the MsgPtr& array
 TotMsgs = TotMsgs - 1
 Adjust = MsgPtr&(MsgToDelete + 1) - MsgPtr&(MsgToDelete)
 For i = MsgToDelete To TotMsgs
     MsgPtr&(i) = MsgPtr&(i + 1) - Adjust
 Next
---------------


MKI$ & CVI in VB:

function MKI$ (Work%)
  MKI$ = Chr$ (Work% MOD 256) + Chr$ (Work% \ 256)
end function

function CVI% (Work$)
  CVI% = (ASC (Left$ (Work$, 1)) * 256) + ASC (Right$ (Work$, 1))
end function
---------------


Using Different Fonts, Colors in a Box:

You can use different colors and fonts with the .Print method in a Picture Box
(during run-time) to get different fonts and colors in a box. You can also use
different fonts and colors in the form itself.
---------------


System Font

In a List box using the System font, 9 pitch is non-proportional, 9.75 is
proportional.
---------------


Focus & Order of Execution:

Some times VB will delaying doing something while it executes more code. For
example, if you write a lot of stuff to a List box in a tight loop, the List
box display on the screen will not be shown updated until the code comes to a
"resting point" (ie: waiting for user input).

Focus on StartUp

If you try to display a form on startup that has your logo, copyright notice,
etc, you may find that the form displays, but the detail on it does not. You
have to force VB to wait until the detail is displayed. One way to do that is
do define a Global variable and when you are finished with the Title box, set
the variable ("Continue", in the example below) to true (-1).

Form1_Load:
TitleForm.Show
Do
  x = DoEvents() 'allow control to the system
Loop Until Continue = -1

Another way is to "Loop Until Len(Label1.Caption) > 0" (You can omit the >0.)
This assumes you have a Label1 box, of course.


Another problem is trying to get some other form to load and to transfer focus
to it during start-up, and then continuing the start-up. What happens is that
you can do a Form2.Show, for example, but after the Form2_Load code (if any)
is run, focus will immediately return to the original form. Users don't get a
chance to interact with Form2. If Form2 is an installation form, for example,
the installation will never get done.

The solution is to use Show Modal (eg: Form2.Show 1), with the possible
drawback being that you have to Unload the form to return to the calling form.
---------------


SetFocus:

You cannot do a SetFocus until a form is visible. Use Show instead.
---------------


Trapping Double-Click before Click:

If you have code assigned to both the Click event AND the Double-Click,
when you double-click, VB will execute the Click code on the first click of
the double and the D-C code on the second click.

Here's how to avoid executing the Click when double clicking (J. Zuck):

On the first Click, setup a timer event to process the click in the "near"
future. If a DblClick occurs within the timer.interval it disables the timer
and processes the DblClick instead.
---------------

Using "Enter" in Place of "Tab":

Sub Text1_KeyPress(KeyAscii As Integer)
    If KeyAscii = 13 Then
       KeyAscii = 0            ' Avoid the beep
       SendKeys "{Tab}"        ' Simulate pressing the Tab key
    End If
End Sub
---------------


Finding An Item Added to a Sorted List Box:

Instead of using List.AddItem, use
SendMessage(hWnd, LB_ADDSTRING, wParam, lParam).

This adds the item to the list and returns the ListIndex of the newly added
item.
---------------


Inhibiting Screen Updating:

If you are adding a whole bunch of items to a list box all at once like
assigning items from an array from within a loop, you might want to use the API
WM_SETREDRAW message to prevent the list box from being continually updated on
the screen as each item is added. Not only does this stop the flickering, but
it makes adding the items much faster.
---------------


Linking Sorted and Unsorted List Boxes:

You can append a 4 byte value to an item in a List Box by using the
LB_SETITEMDATA message and you can retrieve the value using the LB_GETITEMDATA
message. You can use the ItemData to store the location of the item in the
unsorted array.
---------------


List Box: Searching For An Item:

Object: (general)  Proc: FindItem
Sub FindItem (Lst as Control, a$)
U = Lst.ListCount
L = 0
Do
  IF U < L Then
    Lst.Index = I
    MsgBox "Not Found"
    Exit Sub
  I = (L + U) / 2
  IF P = X(I) Then
    Lst.Index = I  'Found. Set ".Index" accordingly
    Exit Sub
  ElseIf P > X(I) Then
    L = I + 1
  Else
    U = I - 1
  End If
Loop

Note: If a match is not found, we still set ".Index" to "I" so that you can
      do an alphabetical insert, if you wish.
---------------


Copying a Drawing to Clipboard

The following will work, IF Picture1.AutoRedraw is True.  (J. Zuck)

Sub Picture1_Click ()
   ClipBoard.Clear
   Picture1.Circle (1000, 1000), 500
   ClipBoard.SetData (Picture1.Image)      'Picture1.Picture won't work.
End Sub

Sub Picture2_Click ()
   Picture2.Picture = ClipBoard.GetData()  'Picture2.Image is illegal.
End Sub
---------------


Drawing - Scale

You can change the scale for pictures from twips to virtually anything you
want (100 allows you to do easy percent drawing), but you still have to draw
by using pixel measurements. I found this out by trying to set DrawWidth to
draw a line covering the middle 20% of a small picture.
---------------


Saving Picture Files:

It seems that SavePicture saves everything when the property is a Picture on a
Form, but it saves only the original loaded file when the property is a
Picture on a PictureBox, and it saves only the new drawn lines when the
property is an Image in a PictureBox.

The following should fix it:

  'BMP or WMF loaded into Picture1
  'Redlines added with Line, Pset, etc., then
  Form1.Picture = Picture1.Image
  PictureSave Form1.Image, "TEST1.BMP"

This works with WMFs but they cannot be changed and saved as WMFs, they get
saved as BMPs, which may or may not be ok with you.
---------------


Printer Setup:

Declare Function LoadLibrary Lib "kernel" (Byval LibName$) As Integer
Declare Function FreeLibrary Lib "kernel" (hLib%) As Integer
Declare Function DeviceMode Lib "HPPCL.DRV" (hWnd%, hLib%, Dev$, Port$)

SUB Command1_Click ()
    hLib% = LoadLib% ("HPPCL.DRV")
    Result% = DeviceMode (hWnd, hLib%,"HP / PCL LaserJet","LPT1:")
    Result% = FreeLibrary (hLib%)
END SUB

Declare Function LoadLibrary Lib "KERNEL" (ByVal lpLibFileName$) As Integer
Declare Sub FreeLibrary Lib "KERNEL" (ByVal hLibModule%)

Then change PSetupMNU_Click to read:

 Sub PSetupMNU_Click ()
   RetStr$ = String$(256, 0)
   RetStrSize% = Len(RetStr$)
   x% = GetProfileString("windows", "device", "", RetStr$, RetStrSize%)

   i% = InStr(RetStr$, ",")
   If i% > 0 Then
     a$ = Left$(RetStr$, i% - 1)
     b$ = Right$(RetStr$, Len(RetStr$) - i%)
     j% = InStr(b$, ",")
       If j% > 0 Then
           DriverName$ = Left$(b$, j% - 1)
           PortName$ = Right$(b$, Len(b$) - j%)
       End If
   End If

   If Len(DriverName$) > 0 And Len(PortName$) > 0 Then
     LibHandle% = LoadLibrary("PSETUP.DLL")
        If LibHandle% >= 32 Then
          r% = DoPrinterSetup(Form1.hwnd, DriverName$, PortName$)
          FreeLibrary LibHandle%
          If Not r% Then MsgBox "Can't run Printer Setup", 64, "Printer Setup"
        End If
   Else
        MsgBox "No default printer selected", 64, "Printer Setup"
   End If
 End Sub
---------------


Printer Control Codes:

The normal Print command in VB will not properly send printer control codes
through to the printer. Here is how to get around VB:

Open "LPT1" For Binary As #1  'note: no colon on LPT1
Put #1,,<put your control codes here>
Close #1
---------------


Printing Forms:

In VB's File menu, there is a Print option for printing forms and code. On an
HPLJ, the top of forms will be cut off because VB/Windows tries to print in
the top-most part of the page, which is, of course, unusable on an HPLJ.
---------------


Calling the Windows 3 Calculator:

Declare Function isapploaded Lib "kernel"(name$) As Integer Alias
        "GetModuleHandle"  'All one line

Sub MAIN
  If isapploaded("calc") = 0 Then
    Shell("calc.exe", 1)
  Else
    AppActivate "Calculator", 0
    SendKeys "% r"
  End If
End Sub
---------------


hWnd for a Control:

All you have to do is use the Windows API GetFocus call after setting the
focus to the desired control:

General Section
  Declare Function GetFocus% Lib "User" ()

In your code
  Command1.SetFocus
  ButtonHwnd = GetFocus()
---------------


.INI Files:

The calls to read/write private ini file are:

GetPrivateProfileInt - returns int value
  WORD GetPrivateProfileInt( LPSTR lpApplicationName
                           , LPSTR lpKeyName
                           , int nDefault
                           , LPSTR lpFileName )

GetPrivateProfileString - returns string
WritePriviteProfileString

The calls to read/write the WIN.ini file are almost the same -- just remove
the Private.
---------------


Multi-Instance App Prevention:

Here's an example of the Multi-Instance App prevention, as well as an example
of calling DLL functions. You simply "Declare" them at the beginning (taking
them from a provided .DEFs file) then use them!  (See the "Dummy" returning
functions lower down.)

Dim PassString$         ' declare a local string to hold user's password

Const SW_SHOWNORMAL = 1
Declare Function GetActiveWindow Lib "User" () As Integer
Declare Function ShowWindow Lib "User" (ByVal hWnd As Integer, ByVal nCmdShow As

Form_Load()                ' this executes when the PassWord form loads

  On Error GoTo NoLink        ' setup our no-link error handler
  SystemLink.LinkMode = 1     ' try to establish a hot link with a server
  On Error GoTo 0             ' disable error trapping

  MsgBox "A DDE LINK HAS BEEN ESTABLISHED WITH A SERVER!!!"

  AppActivate "SalHist"                       ' activate the "other" SalHist
  hActive = GetActiveWindow()                 ' pickup it's hWnd handle
  Dummy = SetFocusAPI(hActive)                ' give it the system focus
  Dummy = ShowWindow(hActive, SW_SHOWNORMAL)  ' and restore its size
  End                                         ' finally, terminate this app!

  On Error GoTo 0             ' disable our temporary error trapping
  SystemLink.LinkMode = 0     ' abandon our local DDE connection attempt
  LinkMode = 1                ' and establish ourselves as a DDE server...

  CenterForm PassWord         ' (My standard.lib form-centering routine)
  PassString$ = ""            ' init the
  PassWord.Show 1             ' present the PassWord form for user data
                              ' entry.  The "1" makes it "modal" (stickier)
End Sub                       ' end of the Form_Load subroutine.
---------------


SendMessage:

In the Global module:
'type the declaration on one line
Declare Function Sendmessage Lib "user" (ByVal Hwnd%,
                                         ByVal Msg%,
                                         ByVal wParam%,
                                         ByVal lparam As Any) As Long

Global Const wm_WinIniChange = &H1A

Then, for example:
Sub Command1_Click
x& = Sendmessage(&HFFFF, wm_WinIniChange, 0, ByVal "")
End Sub
---------------


Windows Termination:

If Windows is about to terminate, it will invoke your Form_Unload procedure. In
that proc, you can do whatever you want, including telling Windows to *cancel*
the shutdown!

To cancel the Form_Unload, use: Cancel=True.
---------------


Cursor (text), Position:

You can get the cursor position in a text box as follows:

CursPos = Text1.SelStart
---------------


Data Entry Masking:

General - Declarations:
  Dim Txt as String
Text1_GotFocus ():
  Text1.Text = "01/23"
  Txt$ = Text1.Text    'Txt$ = "01/23". This intitialization keeps KeyUp
                       '  from kicking in until a key has been pressed.
Text1_KeyPress:        'Assume cursor is on the "/" and "X" is pressed.
  Txt$ = Text1.Text    'Note: Text1.Text is still "01/23" at this point.
Text1_KeyUp
  If Txt$ <> Text1.Text Then  'now Text1.Text is "01X/23"
     Call CursPos(Text1)      'and Txt$ is still "01/23"
  End If
CursPos(Ctl as Control)
  i = 0
  Do While Len(Txt$) > i
    i = i + 1
    If Mid$(Txt$, i, 1) <> mid$(Ctl.Text, i, 1) Then
      pos = i
      Exit Do
    End If
  Loop
  if pos = 3 then  'Example of preventing an unwanted change:
    Ctl.Text = Txt$          'Reset Text1.Text and
    Ctl.SelStart = pos - 1   'put the cursor back where it was.
  end if
---------------


Data Entry Routine:

.FRM Code:
Sub Form_Load ()
   SomeForm.Show
   InitStdControl AcctNo1            'Initialize standard edit controls
   InitStdControl AcctNo2
   .. etc
End Sub

Sub AcctNo1_KeyPress (KeyAscii As Integer)
   StdEdit AcctNo1, KeyAscii         'Use standard editing procedure
End Sub

Module (.BAS) Code:
DefInt A-Z
Declare Function GetFocus% Lib "USER" ()
Declare Function SendMessage& Lib "USER" (ByVal hWnd%, ByVal wMsg%
                                          ByVal wParm%, ByVal lParm&)
Const WM_USER = &H400                'Define the message number for
Const EM_LIMITTEXT = WM_USER + 21    '  limiting the length of an edit
                                     '  Control
Sub InitStdControl (Ctrl As Control)
   Ctrl.Width = 500                  'Make field some standard length
   Ctrl.SetFocus                     'Allow maximum of 14 characters
   Ok& = SendMessage(GetFocus(), EM_LIMITTEXT, 14, 0)
   .. more setup
End Sub

Sub StdEdit (Ctrl As Control, KeyAscii%)
   If KeyAscii >= 48 And KeyAscii <= 57 Then
      Ctrl.SelLength = 1             'This produces overstrike mode
   Else
      KeyAscii = 0                   'Ignore non-numeric keys
   End if
End Sub
---------------



Data Entry Text Limit:

Declare Function GetFocus% Lib "USER" ()
Declare Function SendMessage& Lib "USER" (ByVal hWnd%, ByVal wMsg%,
                                          ByVal wParm%, Byval lParm&)
Const WM_USER = &H400
Const EM_LIMITTEXT = WM_USER + 21

In your Form_Load procedure:
MaxLen% = Whatever
Text1.SetFocus
Ok& = SendMessage(GetFocus(), EM_LIMITTEXT, MaxLen%, 0)
---------------



Text Box - Flicker:

Concatenating text in a Text Box can cause screen flicker. Instead use
SelStart and SelLength - no flicker.
---------------


Flashing Text:

  Sub FlashColor ()
     If FlashState = True Then
         Label1.BackColor = RGB(255, 0, 0)
         Label1.ForeColor = RGB(255, 255, 255)
     Else
         Label1.BackColor = RGB(255, 255, 255)
         Label1.ForeColor = RGB(0, 0, 0)
     End If
     If FlashState = True Then
         FlashState = False
     Else
         FlashState = True
     End If
 End Sub
--------


Flashing Text:

You can use the .SelLength and .SelStart to highlight and unhighlight a
section of the text.
---------------


Timer:

If you use a Timer with a small interval (eg: 1) and another application is
active, Windows may not be able to respond at the specified duration and
execution of the related code will be postponed until Windows can get to it.
---------------
