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,, 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. ---------------