Você está na página 1de 159

Page 1: The Basics

View Page Comments Leave a Comment


What is Visual Basic?

Visual Basic (VB) is a beginner programming language for authoring Windows©-


based software. With an easy-to-use drag and drop window designer, just about
anyone can get started with VB in an hour or so. In the past, VB was known as the
language of the hobbyist, but since version 5.0, professional programmers have
taken notice of the potential of VB. Applications written with VB will only run on
computers with a minimum Windows 95.

About Programming

If you set out to learn a foreign spoken language you would first study it's structure
and then build your vocabulary. The techniques to learning VB follow the same
principles. You will not create a popular video game or a must have Windows
program until you are fluent in the language. Be patient.

It's All About Objects

Program code is written by referring to objects. In the real world, an object is


basically any person, place, or thing. You might also consider non-visible things as
objects. For instance, oxygen cannot be physically seen, but can be represented by
chemical diagrams. Visual Basic follows this same theory of objects; some visible,
some not visible.

Objects have characteristics, or attributes. We call these Properties in VB. Objects


do things and perform actions. We call these the object's Methods. When an object
performs an action, sometimes it produces a tangible result. This is a special type of
Method called a Function. Methods and Functions of an object may require some
specific information peculiar to the situation. These individual pieces of information
are called Arguments, or sometimes parameters.

In order to refer to a Property of an object, we separate the object's name and the
property with a dot. We then use an equals sign to give the Property a Value. Allow
me to show how a real world object would be represented in program code. Let's call
this an Author object.

Author.Name = "Jim"
Author.HairColor = "Brown"
Author.Beard = True
Author.Age = 21

This Author's Name and HairColor properties are text. Literal text must be enclosed
in quotes. The Age property is numeric and does not require the quotes. The Beard
property is Boolean; that means it can only be True or False. I'm sure you realize I'm
referring to myself with this code. Let's call my next birthday an Event. At this event,
I plan to shave my beard and dye my hair blond. Objects and their events are
written with an underscore (_) separator, rather than a dot.

Private Sub Author_Birthday()

1
Author.Age = Author.Age + 1
Author.Beard = False
'This would only happen in code!
Author.HairColor = "Blond"
End Sub

After my birthday party is over, I'm going to get a beer and watch a baseball game.
In other words, I am going to perform two actions. I have a Drink method and a
WatchTV function. Watching TV sometimes makes me sleepy, sometimes it doesn't. I
also have to supply a channel number to the TV or I won't be amused. Let's find out
what the result is.

Private Sub Author_AfterPartyOver()


Dim bSleep As Boolean
Author.Drink "Beer", 4
'Game was on channel 15, was boring, and made me sleepy
'The result is True
bSleep = Author.WatchTV (15)
Author.Sleeping = bSleep
End Sub

My Drink method required a specific beverage and the number of glasses I will drink,
so I supplied "Beer", and 4. I needed a place to store the result of my WatchTV
function, so I created a variable. My Sleeping property was then set to the value of
the variable. Methods and Functions are written similar to Properties, with the dot
separator. The arguments are then written afterwards, with multiple arguments
separated with commas. In the case of a Function, the arguments must be enclosed
in parentheses.

A more advanced programmer can create custom objects with Class Modules. A
Class Module is usually a non-visible code-only definition of an object designed to
perform custom tasks which are not readily available in the VB language. Don't try to
dive into Class Modules until most of this tutorial is old news to you.

Code written with VB does not always use an object's properties, methods, and
functions. VB has done much of the work for you by supplying Global Statements.
The syntax (correct way to write code) varies from statement to statement.

Projects

As a beginner starting a new VB project, always select Standard EXE. ActiveX type
projects are advanced and require a broad understanding of the language. With IIS
and DHTML applications (which are new to VB 6.0), you can create programs
designed for the Internet.

Now right there in front of you is a form. A form is simply a window that you design.
You'll draw controls on the form to create a Graphical User Interface (GUI).

Forms and controls are both objects. Since controls are part of a form, you could say
that a form is the parent object. The importance of the parent/child relationship
between objects becomes more apparent as you learn more about programming.

2
A project (.vbp file) is an "application on the drawing board" and actually
is just a straight text file containing information about your project as a
whole and where your forms and code modules are located on your
computer.

When you start VB, you'll see a window where you'll have the option of
starting a new project, or opening an existing one from either the
existing or recent tab. All of this project-opening can also be done from
the File menu. File/New Project, File/Open Project, or your most recent
files are listed at the bottom of the menu.

The Toolbox

In order to draw controls, you must have the toolbox visible. You do this
by clicking the toolbox icon on the toolbar or selecting Toolbox from the
View menu. Click on the desired control and draw it on a form by dragging the
mouse from the upper left to the lower right positions of where you want the control
to be on the form. Also, double-clicking on the control in the toolbox will place a
default sized control in the center of the form. Afterwards, you can drag the control
to any new position on the form.

The toolbox pictured is all the intrinsic controls available. VB also comes with
custom controls (OCX's) which can be added to the toolbox by going to
Project/Components. You'll find some very useful custom controls here, such as the
ToolBar, StatusBar, RichTextBox, DBGrid (Data Bound Grid), and many others.

Properties Window

You're going to spend a lot of time with the Properties Window. Choose one of 3
ways to display it. My choice is to click on the object (or give it the "focus") and
press F4. You can also right click on the object and select properties from the
resulting pop-up menu. Lastly, select Properties Window from the View menu. The
first property in the list is (Name).

With the Properties Window, you are setting a value to properties at design time.
You can also write code to set property values at run time, but in most cases, the
Properties Window is much easier to work with.

Name Property

The first property you should be familiar with is the Name property. You should
always name your Forms and controls as the first order of business. Constantly, I
see rookie programmers using the default name of a control or form. This default
name is the name of the object and the number 1 for the first object, 2 for the
second, etc. Give your objects descriptive names with the accepted prefix.

Use this universally accepted naming convention:

Object Prefix Example


Form frm frmMain
CommandButton cmd cmdAdd

3
TextBox txt txtInput
Label lbl lblInfo
ListBox lst lstChoices
ComboBox cbo cboCategory
CheckBox chk chkSave
OptionButton opt optQuit
PictureBox pic picText
Image img imgDisk
Frame fra fraBuild
Timer tmr tmrFlash
Data dat datRecords
Project Explorer

The Project Explorer is very useful for getting an overall view of your project, and
switching between forms and code modules. You can use it to add or remove forms
and modules by right-clicking anywhere and using the add commands from the
resulting pop-up menu. To display Project Explorer, click its icon on the toolbar,
select it from the View/Project Explorer menu, or press CTRL+R.

Form Layout Window

Use the Form Layout Window to position your form to where it will be displayed on
the screen. Its icon is available on the toolbar or you can select it from the
View/Form Layout Window menu.

Code Windows

The easiest way to access a code window for any particular form or control is to
double-click on it. Alternatively, you can right-click on it and select View Code, or
click View/Code from the menu when the object is selected. At the top of this window
is two drop down lists where you can switch between the code windows for all the
different objects in that module and their procedures.

In the lower left corner of all code windows, there are two buttons where you can
switch between procedure view and module view. A form and all its constituent
controls comprises a Form module. Try it both ways and decide which is easiest for
you. If you go to Tools/Options/Editor tab/Windows Settings, you can set which view
is the default.

Procedures are the code that runs in response to events. You write code between the
Private Sub and the End Sub line.

Running and Saving Your Project

Press F5 to run your project. Clicking the blue arrow on the toolbar or selecting
Run/Start from the menu will accomplish the same thing. You are no longer in
"design time", but rather "run time". You cannot write code during run time, this is

4
for testing what you've done so far, but be careful, you've got a running program on
your hands.

You'll want to have VB set to save your project before running. If you don't, you may
lose all the changes you've made. This will be very aggravating, trust me. Go to
Tools/Options and click the Environment tab. Make sure the Save Changes option is
selected.

Find this site useful? Please donate any amount to keep it going.

Like music? Please visit my latest project: SONG-DATABASE.COM

[Previous] [Top] [Next] [Main] [Home]


Material contained in this tutorial is Copyright 1999-2005 James S Laferriere and cannot be duplicated in any
manner, shape, or form without expressed, written permission from the author.

Page 2: Setting Up Your Form


View Page Comments Leave a Comment

Before drawing any controls on a Form, you should do the following:

Name the form and save it

A Form has a name (which is how you refer to it in code) and a file name with an
.frm extension. An .frm file is actually a straight text file which contains instructions
that VB uses to create the Form. Load an .frm file into any text editor if you want to
see these instructions. I usually right click on the Form in Project Explorer and select
"Save Form1 As", give the file a name and save it, and then immediately go to the
Properties Window and give the Form a descriptive name with an "frm" prefix.

Decide what kind of border to use

By default, your Form will have the familiar blue title bar on top, a "Form" icon, a
"Form1" title, minimize, maximize, and close buttons, (referred to as the Control
Box), and be sizable at run time. Any of this can be changed while designing the
Form, but only the title (Caption property) and the icon (Icon property) can be
changed at run time.

BorderStyle property

• 0 - None No border, title, icon, or control box. The form will not be sizable,
or able to be moved by the user. You are responsible for writing code to close
the Form at run time. A common setting for games.
• 1 - Fixed Single Same as a default form, but the user cannot resize it.

5
• 2 - Sizable Default
• 3 - Fixed Dialog No minimize or maximize buttons, no Icon, and not sizable
by the user. This setting is used for specialized dialog boxes.
• 4 - Fixed ToolWindow A Tool Window has a slimmer title bar, and no
minimize or maximize buttons. Use this setting as a utility form, such as a
toolbox.
• 4 - Sizable ToolWindow Same as above but able to be resized by the
user.

MinButton, MaxButton, and ControlBox properties

These three properties can be set to True or False. Setting ControlBox to False will
remove all three buttons. Setting MinButton or MaxButton to False will not totally
remove the button, but rather just gray it out so it cannot be used. Setting both
MinButton and MaxButton to False will remove both buttons. You cannot gray out
the close button (the 'x') without some help from API calls and you can only remove
it by removing the entire Control Box.

WindowState property

Set this property at design time to indicate how the Form will be displayed initially.

• 0 - Normal The Form will display according to the size and location you
decided at design time. In most cases, you'll use this setting which is also the
default.
• 1 - Minimized The Form will start up minimized as an icon in the Windows
Taskbar.
• 2 - Maximized The Form will run full screen, but this may not be a good
idea if you're eventually going to run your program on different machines,
due to the different screen sizes. You might think your controls are nicely
placed until you run the Form on a different computer with a larger screen
size and find a lot of empty space or on a smaller screen size and the whole
Form doesn't seem to be there.

StartUpPosition property

If your Form is not maximized, you'll need to place it in an appropriate place on the
screen. You can do this with the Form Layout Window or set the StartUpPosition
property.

• 0 - Manual You'll set the Left and Top properties of the Form in the Form's
Load event.
• 1 - CenterOwner The form will be centered on the form which owns it.
• 2 - CenterScreen The Form will run centered on the screen. A most
pleasing effect in my opinion.
• 3 - Windows Default This is the default setting. You're putting the decision
into the hands of Windows, which can be unpredictable.

Moveable property

6
By default, the Moveable property is set to True, meaning users will be able to move
your Forms by dragging on the title bar. Set this property to False to create a non-
moveable Form. This won't affect whether or not the Form can resized.

Caption property

This is the title of your Form. It will appear on the left side of the title bar next to the
Icon. You can set this property dynamically, (which simply means at run time by
writing code), if your Form has multiple purposes and needs different titles at
different times.

Icon property

Set this property to an icon picture file if you don't like the "Form" icon. Actually,
using the default form icon is somewhat non-professional. The icon can be changed
at run time by setting the property with the LoadPicture function or setting it to a
picture which is already a property of another Form or Control with a Picture
property. The standard Windows icon is also available by going to a Form's
Properties Window, highlighting the (Icon) text and pressing the delete key.

Picture property

Set this property to any valid picture file to set a background for your Form. The
picture file should be of the correct size, you cannot stretch, shrink, center, or tile it
without some help from Bit Block Transfer See Image control for a list of valid
picture file formats.

Page 3: Common Properties


View Page Comments Leave a Comment
Appearance

You can experiment with the different look of the Flat setting and the 3D setting, but
most of the time, you'll leave the default setting of 3D in place.

BackColor

When setting the Backcolor of an object, you'll be offered a drop down color palette.
Choose from these colors or click the System tab and choose a color which is already
in place on the computer (not necessarily your computer, but whatever computer
your program is running on). For instance, you may want the Backcolor of your Form
to match the Desktop. You can't change system colors from here. That's done in the
Windows Control Panel.

7
See also Page3A: Using Colors

BorderStyle

Again, experiment with the Fixed Single and None settings.

DragIcon and DragMode

These two properties are used for "drag and drop operations" of a control. Setting
DragMode to Automatic makes dragging and dropping much easier. Use the Manual
setting when you need to get more specific about your operations. You can set
DragIcon to an Icon (.ICO) file. Users would see that icon as they dragged the
control. You can just use the default icon (arrow pointer in a rectangle) by leaving
DragIcon at the None setting.

See Page 31: Drag and Drop

Enabled

Normally, your controls will be enabled (set to True), but sometimes you may want
to put a control off-limits to the user. Setting Enabled to False could be a visual hint
to the user on how to use your program. You can make certain options available or
not available when the time is right.

Font and ForeColor

Click the ellipsis (three dots) button and the Font Dialog Box is displayed. Here you
can experiment with different fonts, font styles, and font sizes. If you plan on
running your program on other computers, you'll want to select only fonts which the
user is sure to have. Font color is set with the Forecolor property.

See also Page 3A: Using Colors

Height and Width

Use these properties to size your control or form. By default, your controls are
measured in twips. Without a detailed explanation, there are approximately 1,440
twips to an inch.

Left and Top

Use these properties to position a control within your Form, or position the Form on
the screen. Setting the Left and Top Properties to 0 will place the control in the
upper-left portion of the Form. Increasing the value of Left and Top will move the
control across and down respectively. These properties are also known as the X and
Y values.

MouseIcon and MousePointer

8
When the user puts the mouse over a control, you can change the icon from the
default (usually the standard arrow) to any Icon (.ICO) or Cursor (.CUR) file. You can
use the Mousepointer property by itself by choosing one of icons provided in the
Properties Window or, set Mousepointer to 99-Custom and use the MouseIcon (Load
Icon) dialog box to choose your own.

TabIndex and TabStop

Some users are accustomed to using the Tab key to navigate around a window. By
default, the first control you draw has a TabIndex of 0, and when a Form is
displayed, the focus will go to that control. If the user presses the Tab key, the focus
will move to the next control in the Tab order. Change the TabIndex of controls as
needed. Setting a control's TabStop property to False will skip over that control in
the Tab order while maintaining its TabIndex value.

See Page 11: Focus, Focus

Tag and ToolTipText

These two String type properties are used to associate some text with an object.
Surely, you've seen the ToolTipText property in action when you pause your mouse
over a button on a toolbar of a program and a word or two is displayed on a
yellowish background. The Tag property can store some additional descriptive text
about the control, but it's not displayed to the user; it's more for your own use.

Visible

Obviously, this property is used to make objects appear and disappear. Visible goes
one step further than Enabled. When Enabled = False, the control will be grayed out
and not usable. When Visible = False, the control cannot be seen or used, but still
exists.

Move method

You won't find Move in the Properties Window, but this method can be used with
Forms and most controls. Not only can you move an object with this method, but
you can resize it at the same time. You might use Move to create simple animations,
but considerable "flashing" may occur.

'Moves the Form to the upper left corner of the screen


and sizes it one third the screen's Width and Height
myForm.Move 0, 0, Screen.Width / 3, Screen.Height / 3

'Moves a command button to the center of the Form


cmdBegin.Move (myForm.Width - cmdBegin.Width) / 2, _
(myForm.Height - cmdBegin.Height) / 2
Move has four arguments- Left, Top, Width, and Height, but only the Left argument
is required.

9
See Page 8: How To Write Code

ZOrder method

The Left and Top properties can be considered the X axis and Y axis of an object, but
there is also a Z axis. Although the screen is not physically three-dimensional,
objects are virtually layered from bottom to top, or back to front. You can set the
Zorder of object at design time by right-clicking on them and selecting "Bring to
Front" or "Send to Back". At run time, using the ZOrder method will accomplish the
same thing.

'Send to Back
imgLogo.ZOrder 1

'Bring to Front
imgLogo.ZOrder 0
There are three graphical layers to consider: The back layer is where background
pictures and drawing exists, the middle layer is graphical controls, and the front
layer is non-graphical controls. When using ZOrder, the object is sent to the front or
back of the layer it exists on, so it's not possible, for instance, to send a
CommandButton behind a Shape control.

Page 4: Intrinsic Controls


View Page Comments Leave a Comment
Command Button

The most familiar of all controls is the Command Button. The Command Button can
be of 2 styles- graphical and standard. The standard Command Button displays text
describing what the button does, while the graphical style can display a picture, text,
or both. You cannot insert a picture or change the background color (BackColor) until
you set the Style property to 1-Graphical. The Style property is read-only at run-
time, therefore you must decide which style to use at design-time.

Often, you'd want a Command Button to be the default button of the form which
means if the user presses the enter key, the code in the button's Click event will run.
Commonly, you would set the button's Default property to True if that button
accessed the most common function of that form. On the other hand, you can set
the Cancel property to True. This would run the button's code if the user pressed
the escape key. These two properties would be perfect for "OK" and "Cancel"
buttons.

Another way to run the code in a Command Button's Click Event is to set the Value
property to True. This will only work at run-time, you won't find this property in the
Properties Window.

cmdEnter.Value = True
Even another way to run a button's Click Event is to set up an access key. This
allows to user to hold the ALT key and press one letter in place of actually clicking
the button. Do this by adding an ampersand (&) in front of the desired letter while

10
typing the Caption property. That letter will be underlined on the button to indicate
that an access key is active.

Text Box

The Text Box is a heavily used control which allows users to enter and edit text. If
you want a control which just displays text, then use a Label. You can only use one
style of text in a Text Box. For instance, half bold and half italics is not possible.
You'll need a RichTextBox control for that.

The Text property holds whatever text is in the control. Use the Alignment property
to left, right, or center justify the text. The Locked property can be useful for
switching between an editable and a not editable Text Box. Setting the MaxLength
property to anything other than 0 will limit the number of characters which can be
entered. The PasswordChar property is commonly set to an asterisk (*) to have
the Text Box serve as a field for entering a password. If you desire, the Text Box can
have scroll bars horizontally, vertically, or both. Set the Multiline property to True if
you want your Text Box to automatically word wrap.

The SelText property holds whatever text is highlighted (selected); this is most
useful when adding cut, copy, and paste functionality to your program. The
SelLength property will tell you how many characters are highlighted. The SelStart
property tells you where highlighted text begins, or, if no text is highlighted, it tells
where the insertion point is.

In this Text Box, the value of SelText is "score and seven", the value of SelLength is
15, and the value of SelStart is 5 (meaning there are 5 un-highlighted characters
before the SelText).

If a Text Box loses the focus, any highlighting will disappear and then only return
when the Text Box gains the focus again. But that is only the default behavior; set
the HideSelection property to False to keep the highlighted text as is no matter
what control is active.

Label

A Label's main purpose is to display some descriptive text. Like a Text Box, you can
have the text left, right, or center justified. You can also have the Label
automatically size itself to fit its Caption by setting AutoSize to True. A WordWrap
property is also available. Having the Label's Backcolor match that of it's container
(forms, picture boxes, and frames are containers) is done by setting BackStyle to
Transparent. Use BackColor only if you want different colors.

Like Command Buttons and some other controls, Labels can have access keys by
using an ampersand(&) within the Caption. Normally, an access key runs the code in

11
that control's Click event, but an access key in a Label merely sets the focus to the
next control in the tab order.

To actually display an ampersand in the Label, use a double ampersand. For


instance, to display "Stars & Stripes" in a Label, you would need to use "Stars &&
Stripes". Or, the UseMnemonic property can be set to False to "turn off" the access
key, thereby displaying ampersands.

List Box:

A List Box presents a list of choices to the user. If the list items are known at design
time, you can add them in the Properties Window; Click list, and a small edit area
appears. Type the first item and then hold the Ctrl key while pressing enter in order
to type another list item. Users are familiar with double clicking list items to make a
selection, so most of the time you'll be writing code for the List Box's DblClick event.
Setting sorted to True will automatically alphabetize the list. To display check boxes
next to the list items, set the Style property to 1-checkbox.

A separate page is devoted to List Boxes:


Page 5: Working with ListBoxes

Combo Box

A Combo Box is a combination List Box/Text Box. Usually, you'd use this control
when space is at a premium and a List Box just won't do.

A separate page is devoted to ComboBoxes:


Page 6: Working with ComboBoxes

Image

Nothing fancy, an Image control will display a picture. In the Properties Window,
click the ellipsis (three dots) button to bring up a dialog box which allows you to
select a picture file from your computer. The following picture formats are
acceptable: .BMP, .DIB, .GIF, .JPG, .WMF, .EMF, .ICO, .CUR. Animated picture files,
such as the animated .GIF's you'd see on a web page will not work without a special
control which can be downloaded here. Stay away from large .BMP's, they'll increase
the size of your program dramatically. Setting the Stretch property to True will "blow
up" your picture to the size of the drawn Image control. However, considerable
distortion will occur if you stretch the picture abnormally. So don't go trying to
stretch a small graphic to be the background of your form.

Line and Shape

When lines and shapes need to be drawn, you can use these controls or draw them
directly with graphics methods. The controls are easier to use and never need to be
redrawn.

The BorderWidth property governs the thickness of the line or border in pixels.
Dashed and dotted lines or borders can be drawn by setting the correct BorderStyle
property, however, BorderWidth must be set to 1 in order to use these styles.

12
Experiment with the DrawMode property to logically combine the Line or Shape with
the background.

With the Line control, the X1 and Y1 properties represent the coordinates of one
end of the line, while the X2 and Y2 properties represent the other end.

Both the Line and Shape controls are graphical controls having no events to write
code for.

Picture Box

Like an Image control, a Picture Box will display a picture, but it does much more.
Opposite of the Image, a Picture Box has an AutoSize property which when set to
True will size the Picture Box to fit the picture you inserted into it. A Picture Box is a
container control, meaning you can place other controls within it, draw graphics and
print text on it. It's kind of a form within a form.

The Picture Box is the only intrinsic control which can be placed directly on an
MDIForm. The purpose of that would be to create Toolbars and StatusBars without
using a custom control (OCX). The Align property determines whether the Picture
Box functions as a Toolbar (Align = 1) or a StatusBar (Align = 2). When using a
Picture Box like this, the Width will be automatically adjusted to fit the Width of the
MDIForm.

Frame

Frames are also container controls, and are used basically to visually separate areas
of the form. You don't write much code for Frames, they just make your program
look more professional. Don't try to draw a Frame over existing controls, you should
draw the Frame first and then draw its constituent controls inside of it. Controls
could also be cut and pasted into the Frame.

Check Box

Use a Check Box like an on/off switch. The Value property holds the condition of the
Check Box. Checked is on. Unchecked is off. You can set the Style property to
graphical and insert a picture file. This turns the Check Box into a "push button"
on/off switch where pushed in represents "on". If you go with the standard Style,
you can set the Check Box to be on the right or left side of the Caption with the
Alignment property, but users are accustomed to seeing the Caption on the right side
of the box.

Option Button

Option Buttons, like Check Boxes, can be "on", or "off", but you'll use Option Buttons
in a group where only one can be "on". If the user selects one of the options, all
others are deselected. For instance, if you were offering a choice between four colors
for a form's background, you'd use Option Buttons because you can't set two colors.
All Option Buttons drawn on a form are one group. If you need more groups, then
you'll have to draw additional Option Buttons in a container control (Picture Box or
Frame). Option buttons might also be referred to as Radio buttons.

13
Timer

Timers execute code repeatedly according to the Interval you specify. Set the
Interval property in milliseconds. For example, 2000 = 2 seconds. Timers are useful
for checking programs conditions periodically, but don't get in the habit of using
them for everything. A Timer control is not a clock and should not be relied upon to
keep accurate time.

Data Control

The Data Control provides a link to database files.


See Page 25: Database Basics

HScrollBar and VScrollBar

You'll use the scroll bar controls for such things as allowing the user to set a value
from a range, or to measure progress. Set the Min and Max properties to the lower
and upper bound of the range. The Value property will indicate the current position.
Set the SmallChange property to allow the user to increase or decrease the Value
by clicking on the arrows, and set the LargeChange property to respond to a click in
the area within the position indicator and the arrows.

In the diagram below, Min has been set to 1, Max to 20, and Value is currently at 10.

You can, of course, work the scroll bar yourself by setting the Value property in code.

Available sample project:


Scrolling a Picture (29K)
How to use Horizontal and Vertical scroll bars to scroll a large picture.

Page 5: Working With ListBoxes


View Page Comments Leave a Comment

ListBoxes can either be set up to allow to user to select one item from the list, or to
allow multiple selections. Multiple selections are not allowed if you want to display
check boxes next to the list items. If your list is too long for the size you've drawn
the ListBox, scroll bars will appear automatically. When the user selects (clicks) a list
item, it is highlighted and the ListBox's ListIndex property is automatically changed

14
to hold a value corresponding to the item. The first item in the list is numbered 0.
Use the ListIndex property by itself to determine what the user selected, or use it in
combination with the List property.

Private Sub lstChoices_Click( )


MsgBox "You selected " & lstChoices.List(lstChoices.ListIndex)
End Sub
Just remember that the ListIndex property is the number of the selected item and
the List property is the text. The List property is a property array of strings, so don't
try to use it without a subscript.
You can select an item in code by simply setting the ListIndex.
lstChoices.ListIndex = 2
Text property

The above on the List and ListIndex properties serves as an introduction to these
properties, but there is an easier way to get the selected item.

lstChoices.Text
This code is equivalent to
lstChoices.List(lstChoices.ListIndex)
When no item is selected, the value of ListIndex will equal -1. You could use that in
an If statement to run code which required a selected item.
If lstChoices.ListIndex = -1 Then
MsgBox "No item selected"
Else
MsgBox "You selected " & lstChoices.Text
End If
AddItem

Creating the list for the ListBox can be done at design time in the Properties Window
as described in Page 4 or at run time with the AddItem method. This method has one
required argument and one optional argument.

lstChoices.AddItem "Rolls Royce"


lstChoices.AddItem txtCars.Text
lstChoices.AddItem sModel$, 5
The first line adds some literal text to the end of the list. The second line adds to the
end of the list what the user has entered in a Text Box. (You'd probably use a
Command Button with a caption of "Add to List" and put the AddItem code in the
button's Click event.) The third example adds the value of a string variable to the list
and uses the optional argument to give it a certain place within the list at the same
time. This value would be become the new item's ListIndex and all items already in
the list with this ListIndex or higher would be pushed down one in the list and
automatically given a new ListIndex, obviously one higher. If the value of this
optional argument is higher than the ListCount property, an trappable error will
occur.
RemoveItem method

The RemoveItem method deletes a list item; it's one argument is the ListIndex of the
item you wish to remove. Important to consider when removing list items is that the
ListIndex of the remaining items will change to reflect their new positions in the list.
For example, to remove the first five items in a list all at once, you'd need to use a
loop and delete ListIndex 0 five times as shown in this example:

For i% = 1 To 5

15
lstChoices.RemoveItem 0
Next i%
Clear method

The Clear method deletes the entire list at once and requires no arguments. This
would be useful if you were rebuilding the entire list at run time.

lstChoices.Clear
Sorted property

Setting the Sorted property to True will alphabetize your list, or in the case of
numeric list items, arrange them from lowest to highest (but only by the first digit).
To arrange numeric list items by actual value, you'd have to write a function which
loops through all the list items, testing values along the way, and inserting the items
in the proper order. Lists with regular text and numeric text would sort 0-9 followed
by A-Z.

Multiple column ListBoxes

The standard "Open" dialog box, which most applications have, is an example of a
ListBox with multiple columns. Setting the Columns property to the number of
columns desired will create this for you and horizontal scroll bars are automatically
added when the list doesn't entirely fit.

Setting the number of columns might not give the results you expect. For instance, if
you have 10 list items and 5 columns, you would think each column would contain 2
items. But it all depends on the width and height of the ListBox. If you draw the
ListBox with enough Height to fit all the items in one column, then that's what you'll
get, one long column and four blank ones. Also important to remember is that the
width of one column is equal to the width of the ListBox divided by the number of
columns.

Once you create a multiple-column ListBox at design time, you cannot revert back to
a single column at run time, but you can change the number of columns.

Multiple selection ListBoxes

You can set the MultiSelect property one of two ways to allow the user to select more
than one item. With the Simple setting, a mouse click will select (highlight) or
deselect any number of items in any order. With the Extended setting, the user can
hold down the mouse and drag it to select several consecutive items. For keyboard
fans, holding down the Shift key and pressing any of the arrow keys will accomplish
the same thing.

Multiple selection can be used with multiple columns.

Detecting multiple selections is much different. The ListIndex property is no longer in


play here. You must loop through the entire list, checking the Selected property for
a True value.

For i% = 0 To lstChoices.ListCount - 1
If lstChoices.Selected(i%) = True Then
MsgBox "Item number " & i% & " is selected"

16
End If
Next i%
To determine the number of items selected use the SelCount property.
If lstChoices.SelCount > 5 Then
MsgBox "Maximum 5 selections allowed"
End If
NewIndex property

The NewIndex property holds the ListIndex value of the most recently added item.
This could be useful after using AddItem on a long sorted list. Without worrying
about the new item's ListIndex, you could identify the list item and further deal with
it. The most common use for this is in conjunction with the ItemData property.

The ItemData property holds additional numeric information about an item.

lstChoices.AddItem "Rhode Island"


lstChoices.ItemData(lstChoices.NewIndex) = 13
The number 13 is now associated with the "Rhode Island" list item. (Rhode Island
was the 13th state).

The value of NewIndex becomes -1 after using RemoveItem.

TopIndex

The TopIndex property is used to display a certain item in the topmost portion of the
ListBox. This doesn't change the position of items within the list, nor does it affect
the ListIndex property. You could use this property to similate scrolling.

Private Sub tmrScroll_Timer( )


lstChoices.TopIndex = lstChoices.TopIndex + 1
End Sub
Try this example with a Timer Control's interval property set to 1000.
Scroll event

This event triggers when the user scrolls through the list, regardless of the scrolling
method. By scrolling method, I mean clicking the up or down arrow, clicking within
the scroll bar for faster up/down scrolling, or dragging the position indicator. You'd
probably use this event to manipulate other controls which are somehow associated
with your ListBox.

Page 6: Working With ComboBoxes


View Page Comments Leave a Comment

Most of what you already know about ListBoxes will apply to a ComboBox. Items are
added, removed and cleared with the AddItem, RemoveItem, and Clear methods.
List, ListIndex, ListCount, NewIndex, and ItemData properties all the work the same
way, however ComboBoxes cannot have multiple columns or handle multiple
selections.

The Style property is read only. That means you must decide which style to use at
design time and an error will occur if you try to set the value of the property at run
time. There are three styles to consider.

17
• The Simple style appears like you've drawn a TextBox just over a ListBox. If
the user highlights (clicks) one of the items in the ListBox portion, the text
automatically appears in the TextBox portion. Alternatively, the user can elect
to input their own text directly into the TextBox. Either way, the Text
property will hold the user's selection. This is the only style of ComboBox
which will respond to a double click and the double click must come from the
ListBox portion of the control.
• The Dropdown Combo is probably the most used style. This style can be
drawn as wide as it needs to be but it's Height is limited to the space needed
for one line of text. (This is governed by the FontSize). By default, the Text
property will be set to "Combo1", so you should set it's initial value in the
Properties Windows or in code (Form_Load event). The user will either select
an item from the drop-down list or type in their own. Your first thought might
be to use the Change event to respond to the user selecting an item, but
oddly enough the Click event handles this.
• The Dropdown List does not take user input. The Text property is read-only
with this style, unless you assign a string in code which is already a list item.

Private Sub Form_Load( )


cboList.Text = cboList.List(3)
End Sub
Other than this one difference, the Dropdown List style works identically to the
Dropdown Combo.
Since the Dropdown Combo and Simple styles are part TextBox, you can use some of
the TextBox properties like Locked, SelText, SelLength, and SelStart.

Page 7: Events
View Page Comments Leave a Comment

Visual Basic programs are event-driven, meaning code will run only in response to
events. Each object has its own set of events, but many are common to all objects.
Events can be triggered by the user, such as clicking a button, triggered by your own
code, and even by the actions of your program, such as start-up and shut-down.

Change

The most common use for this event is with a TextBox, but it does occur in other
controls, like ComboBox, Label and some others. This event fires when the contents
(most often the text) of a control have changed, so for every key a user types, you
can check for valid data entry, or limit what can be entered.

Click

The most common event. Users expect something to happen when they click things,
so you'll be writing a lot of code for this event. Just about every control has a click
event.

Double Click

18
Abbreviated DblClick in a code window. Keep in mind that DblClick triggers two Click
events, so don't write conflicting code in both events.

DragDrop

Triggered at the completion of a drag and drop operation. Or in other words, when
the user releases the mouse after dragging an object to another location.

See Page 31: Drag and Drop

DragOver

Triggered constantly as an object is being dragged. Use this event to control or limit
the drag and drop operation. Don't write any long drawn out code here.

See Page 31: Drag and Drop

GotFocus and LostFocus

GotFocus occurs when the focus has switched to an object. This can be triggered by
the user clicking on it, or tabbing to it. Since only one object can have the focus,
another control will receive a LostFocus event at the same time.

KeyDown, KeyUp, and KeyPress

Use these events to capture what the user is typing. More often, you'll use KeyDown,
but you may run into a situation where you need to respond to releasing of a key.
These two events can detect just about any key typed including the state (up or
down) of the Ctrl, Alt, and Shift keys. To check for standard letter and number keys,
use KeyPress, just remember one press of a key triggers all three events.

See Page 9: Handling Keyboard Input for more information.

MouseDown, MouseMove, and MouseUp

Similar to the Key events above, these events are triggered by mouse actions.
MouseMove can be triggered several times per second, so let's not write complex
calculations in this event.

See Page 10: Handling Mouse Actions for more information.

Scroll

If an object has scroll bars (ListBox, ComboBox, etc.) this event fires when the user
clicks the up or down arrow or drags the position indicator. This event does not apply
to a TextBox with scroll bars.

Form Events

19
Several Form events will fire starting with the moment of creation. Each has its own
purpose and triggers in a certain order. When the Form is first created, an Initialize
event will occur, followed by Load, Resize, and Paint. Upon destruction of the Form,
events triggered are QueryUnload, Unload, and Terminate.

Initialize

This event occurs only once when the Form is created. It will not fire again unless
the Form is Unloaded and Loaded again.

Load

Occurs when a Form is loaded. This event can only be fired again if the form is
unloaded and loaded again. If a Form loses the focus (thereby losing it's status as
the ActiveForm) and then becomes active again, an Activate event will fire.

Resize

Even though no physical resizing has occured, this event triggers after Load. It will
also be set off by minimizing, maximizing, restoring, and resizing.

Paint

This event fires for the purpose of drawing graphics on the form.

Activate and Deactivate

If a form becomes the ActiveForm (has the focus), Activate will fire, followed by
GotFocus. If another form becomes active, the LostFocus event will occur, followed
by Deactivate. However, GotFocus and LostFocus will only trigger when the form has
no enabled controls.

QueryUnload

This event is used for checking to make sure it's OK to shut down. In this event, you
can actually stop shut-down and even check if the user is trying to shut-down, your
code invoked shut-down, or if Windows itself is responsible. QueryUnload has two
built-in parameters for dealing with these situations- Cancel and UnloadMode.

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer


If MsgBox("Are you sure you want to shut down?", vbYesNoCancel) <> vbYes Then
Cancel = True
End If
End Sub
Cancel = True means "cancel the unloading of this form". You would probably check
some program condition before deciding to set Cancel to True, or you could use a
simple message like the above example.
Unload

After having passed through QueryUnload, this event fires and any code which aids
in having that form out of memory is processed.

20
Terminate

Like Initialize, this event will only occur once during the existing of the Form. It won't
fire if the program is terminated abnormally.

Page 8: How to Write Code


View Page Comments Leave a Comment

In the old days, an application was started and it's code ran top down in a linear
approach, occasionally pausing for user input. VB runs code in response to events.
The events can call sub-routines and the sub-routines can further call their own sub-
routines.

When the code in an event and all it's associated sub-routines finishes, the program
enters idle time, and patiently awaits the next event. Events are actually built-in
sub-routines. All Event Sub routines begin with Private Sub and the name the object
and event separated by an underscore. Ex. Form_Load, cmdOK_Click,
picViewPort_Paint

Dim and On Error statements are generally the first lines of code in a routine.
Option Explicit

This statement can only be placed into the general declarations section of a Form or
Module. It means, "All variables used must be explicitly declared". You can type it in
yourself or go to the Tools/Options menu, Editor tab and check the "Require variable
declaration" box to set Option Explicit as the default. The statement will then appear
in any newly created Forms and Modules automatically. If this statement is absent,
you can name and use variables as you need them, but this is quite amateuristic and
may cause problems.

Outdated statements

Very common in the old style of programming was to branch to different areas of
execution and optionally return with the GoTo, On...GoTo, On...GoSub, and
GoSub...Return statements. Avoid these statements. Nowadays, we create
separate Sub and Function procedures and call them when needed without using
these statements. The only exception to this would be using a GoTo with an On Error
statement. GoTo's branch to a line label or line number within the procedure.

Assignment statement

An assignment statement will set a value to a variable or property with an equals


sign. The property or variable is placed on the left side of the equals sign and the
new value is placed on the right. Assigning values overwrites any previous values.

Private Sub Form_Load( )

21
Dim sTitle As String
On Error Resume Next
sTitle$ = "My program"
frmMain.Caption = sTitle$
iValue% = vbMaximized
frmMain.WindowState = iValue%
End Sub
Note: It's not necessary to assign a value to a variable and then assign the variable's
value to the property. This is just for demostrative purposes.

If it's easier for you, you can precede the assignment statement with the Let
keyword, but this practice is mostly obsolete.

Let frmMain.Caption = "My program"


Let frmMain.WindowState = vbMaximized
iValue% = iValue% + 1
At first glance, there would seem to be a logic error here. How can anything be equal
to itself plus one? The idea here is: Old value = New value, or "Let the value of the
iValue% variable now equal one more than was it is." Often, this is referred to as
incrementing the value of the variable.

This same theory can be applied to strings.

frmMain.Caption = "My program"


frmMain.Caption = frmMain.Caption & "!"
The caption of the form becomes "My Program!"
See Page 19: Working with Strings
To explore the various properties and methods of a particular object, type
the name of the object and a dot in a code window. All the properties and
methods available to that object will be displayed in a pop-up list.
With...End With

Suppose you need to use several properties and methods of an object at once. You
could write:

frmMain.Caption = "some caption"


frmMain.BackColor = vbWhite
frmMain.ForeColor = vbBlue
frmMain.WindowState = vbMaximized
frmMain.KeyPreview = True
frmMain.Show
Using a With block provides a quicker way to write all this and will also speed up the
execution a little because VB won't have to qualify the object over and over again.
With frmMain
.Caption = "some caption"
.BackColor = vbWhite
.ForeColor = vbBlue
.WindowState = vbMaximized
.KeyPreview = True
.Show
End With
Use the Form or control's name after With and then begin using properties or
methods of the object by just typing the dot. This is especially useful in a code
module where you can specify the form and the control.
With frmMain!txtCriteria
.Text = "some text"
.BackColor = vbWhite
.ForeColor = vbBlue
.Locked = True
.Height = .Width

22
.Left = 0
End With
The ! separator is used here as merely a visual way to separate the Form from the
control. The dot can also be used.
Default properties

Every intrinsic control has a default property. Code can be written a little quicker by
using these default properties. It's done by just using the name of the control; the
default property will be assumed.

'This line of code...


txtYield.Text = "4 Servings"
'...can be shortened to this
txtYield = "4 Servings"
Here's the entire list of default properties:
Control Default Property
TextBox Text
Label Caption
OptionButton Value
CheckBox Value
CommandButton Value
Frame Caption
ComboBox Text
ListBox Text
PictureBox Picture
Image Picture
Timer Enabled
Data Caption
HScroll, Vscroll Value
DriveListBox Drive
DirListBox Path
FileListBox Path
Shape Shape
Line Visible
OLE Action
Default object

When you need to refer to a property of the Form, and the code will be located in
that Form, you can omit the Form's name and just write the property. Here's an
example which uses both the default object and the default property of the control.

Private Sub Picture1_Click()


'Shortened from Picture1.Picture = Form1.Icon
Picture1 = Icon
End Sub
Line continuation characters

Sometimes lines of code are too long to be viewed entirely in the code window
without scrolling. One line of code can be continued on the next line by typing a
space and an underscore. Then press enter and keep typing as if you were still on
the first line.

23
If iCount = 0 And cmdOK.Enabled = True And _
frmMain.BackColor = vbBlack And txtInput.Locked = False Then
The maximum number of line continuations is 24.
Multiple statements on one line

As long as individual statements are separated with a colon, you can put as many as
you want on one line, but don't get too carried away, the code may become hard to
read.

For i% = 1 to 10:lstMain.AddItem sData(i%):Next i%

With picLogo
.Left = 0:.Top = 0:.Width = frmStart.Width / 2:.Height = 2400
End With
Commenting your code

It's a very good idea to put your own personal comments into the code. You might
put your project down for a week or so and not recognize what you were trying to
accomplish. Also, commenting enables others viewing your code to understand it.

You can insert comments anywhere in the code window (even before Private Sub and
after End Sub) by typing an apostrophe and then the comments. By default, your
copy of VB will display the comments in green text to distinguish it from keywords
(which are in blue text by default) and other code (black text). These colors, and
also the font of text in a code window, can be customized by going to Tools/Options
windows - Editor Format tab.

Rem statement

The Rem (short for remark) statement is an old-fashioned way to comment, but is
still available to use. Begin a line with the Rem keyword and then type your
comments. Personally, I don't know anyone who hasn't graduated from Rem to the
apostrophe.

Indenting your code

Your lines of code should be indented in the right places for easy readability. Blocks
of code which are related in some way should be separated from other blocks with a
blank line or two. Many code blocks will have required beginning and ending
statements. The code between these statements should be indented. Examples of
this indenting practice can be seen throughout this tutorial.

Start up Form or Sub Main?

When your program is started you can either display the start-up Form or run the
Sub Main procedure.

By default, the start-up Form is the first Form created in the project. You can change
the start-up Form or specify the Sub Main procedure as the Start Up object by going
to Project/ yourproject Properties and selecting a different Start-up object from the
General tab.

24
If a Form is the Start Up object, then any initializing code you may have would be
placed in the Form's Load event. Using Sub Main as the Start Up object allows you to
run initializing code before displaying any Forms. You must eventually display at
least one Form in the Sub Main procedure or you will have no interface. The Sub
Main procedure must be manually created like any other general procedure.

Show, Hide, Load, and Unload

Once your program is initialized, you then Show, Hide, Load, and Unload Forms as
necessary. Showing a Form will both Load it into memory and display it. You might
need to initialize the Form before displaying it. In that case, Load it first, then display
it with the Show method.

Hiding a Form will keep it in memory and basically make it invisible. Unloading a
Form will Hide it and destroy the memory allocated to it. Each has it's own
advantages. If you need to re-display a Form which is hidden, then simply Showing it
is much quicker than re-Loading it, but uses more memory. While building your
application you must always keep in mind the amount of memory you are using and
keep the program running quickly and smoothly. Slow, memory hogging applications
are aggravating to the user.

'Load a Form
Load frmMain

'Show a Form
frmMain.Show

'Show a Form modally


frmMain.Show vbModal

'Unload a Form
Unload frmMain

25
'Hide a Form
frmMain.Hide
By default, a Form will display modeless. What that means is the user can choose to
ignore it and go about their business elsewhere in your application. Showing a modal
Form forces the user to respond to the Form before being able to continue and also
halts the code. VB's built-in message box (MsgBox) is an example of a modal Form.

A modal Form must then be Hidden or Unloaded in order for the code to continue.
Below is a simple example which stops program execution and asks the user what
their name is and stores it in a string variable. Assume this Form has a TextBox
named txtName and an OK button.

Private Sub Form_Load()


Dim sName As String

'Code stops running until frmName is hidden or unloaded


frmName.Show vbModal

sName$ = frmName.txtName.Text

End Sub

Private Sub cmdOK_Click()

Unload frmName

End Sub

The Show method has a second optional argument - the "owner form".

'Show modeless with an owner


frmToolbox.Show , frmMain
A Form can be virtually "attached" to another Form. This will serve two purposes: the
shown Form will always "stay on top" of the owner Form and will unload
automatically if the owner Form is unloaded.
End statement

The End statement stops running your program. In some cases, this may not free all
memory you've used. You may want to use this procedure which specifically unloads
all open Forms and doubly makes sure all memory allocated to Forms is released.

Public Sub EndProgram()

Dim frm As Form

For Each frm In Forms


Unload frm
Set frm = Nothing
Next frm

End Sub
Make sure to put this procedure in a standard module. You can call the procedure
from a Form, but don't put the code here, this will cause errors.
Public Sub Form_Unload( )

EndProgram

End Sub

26
Page 9: Handling Keyboard Input
View Page Comments Leave a Comment

If you're not familiar with character codes, and the difference between Keycode and
KeyAscii, go to Character codes.

A quick way to find out what the KeyCode or KeyAscii value of a certain key is, is to
put a test message box in the appropriate event.

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)


MsgBox KeyCode
End Sub

Private Sub Form_KeyPress(KeyAscii As Integer)


MsgBox KeyAscii
End Sub
Once you know the KeyCode or KeyAscii value, use an If-Then-Else or Select Case
statement to run code pertaining to the key typed. This sample code moves an
object with the arrow keys.
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case 37 'Left arrow
imgMain.Left = imgMain.Left - 40
Case 38 'Up arrow
imgMain.Top = imgMain.Top - 40
Case 39 'Right arrow
imgMain.Left = imgMain.Left + 40
Case 40 'Down arrow
imgMain.Top = imgMain.Top + 40
End Select
End Sub

Personally, I like finding the specific numeric values, but there are actually a whole
pile of built-in constants for KeyCode values which can be used instead.

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)


Select Case KeyCode
Case vbKeyLeft
imgMain.Left = imgMain.Left - 40
Case vbKeyUp
imgMain.Top = imgMain.Top - 40
Case vbKeyRight
imgMain.Left = imgMain.Left + 40
Case vbKeyDown
imgMain.Top = imgMain.Top + 40
End Select
End Sub
This example allows only numeric input in a TextBox.
Private Sub txtInput_KeyPress(KeyAscii As Integer)
Select Case KeyAscii
Case 48 To 57
'Ok to type these keys
Case Else
KeyAscii = 0
End Select
End Sub

27
Setting KeyAscii to 0 (the character code for null) cancels the keystroke. Actually,
you can set KeyAscii to any valid code in order to change what the user typed to a
different character. (You might be able to have some fun with that one).

The Shift argument holds a value which will tell you the state of the Shift, Ctrl, and
Alt keys. The value will be between 0 and 7.

• If Shift has a value of 0....none of these keys are being held down
• If Shift has a value of 1....Shift key is being held down
• If Shift has a value of 2....Ctrl key is being held down
• If Shift has a value of 3....Shift key and Ctrl key are being held down
• If Shift has a value of 4....Alt key is being held down
• If Shift has a value of 5....Shift key and Alt key are being held down
• If Shift has a value of 6....Ctrl key and Alt key are being held down
• If Shift has a value of 7....All three keys are being held down

Here's an example which tests to see if the user is typing Ctrl + X


Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode = Asc("x") Or KeyCode = Asc("X") Then
If Shift = 2 Then
'Code which runs in response to Ctrl + X
End If
End If
End Sub
I was actually being a little lazy in this one, I didn't know the KeyCodes for the letter
x so I had the Asc function get them for me.

VB supplies some more built-in constants (vbCtrlMask, vbAltMask, vbShiftMask) to


detect the state of the Ctrl, Alt, and Shift keys, but the technique to using them is a
little too confusing for the average VB coder. This is straight from the help file:

The constants act as bit masks that you can use to test for any combination of keys.
You test for a condition by first assigning each result to a temporary integer variable
and then comparing shift to a bit mask. Use the And operator with the shift
argument to test whether the condition is greater than 0, indicating that the modifier
was pressed, as in this example:

ShiftDown = (Shift And vbShiftMask) > 0


In a procedure, you can test for any combination of conditions, as in this example:
If ShiftDown And CtrlDown Then
Sorry, Bill, I'd rather keep the handy list of specific values above.
KeyPreview property

The keystrokes typed by the user will be sent to the control which has the focus (the
Active Control) unless you set the KeyPreview property of the Form to True. This
enables you to put all keystroke handling code in the Form's Key events. Otherwise,
you'd have to put the code in every control.

Note: After the Form analyzes the keystrokes, they are still sent to the Active
Control. To prevent that, deaden the keystroke by setting KeyAscii or KeyCode to 0
(null).

SendKeys statement

28
I always seem to find a use for the SendKeys statement. What this does is sends a
keystroke to your application as if were actually typed. Whatever action is performed
by the keystroke occurs. Enclose what you want typed in quotes.

SendKeys "T"
SendKeys "This whole sentence will be sent"
The first example sends an upper-case "T" to your application, the second sends an
entire string. You can also send function keys (F1, F2, etc.), combination keystrokes
(Ctrl + C, Alt + F4, Shift + F2, etc.), and action keys (Enter, Delete, PageUp,
PageDown, etc.), but this done a little differently. You'll need to enclose some codes
within braces and quotes.

Here are a few examples, for more special codes, see your help file.

SendKeys "{F1}" 'F1


SendKeys "{BS}" 'Backspace
SendKeys "{DEL}" 'Delete
SendKeys "{ENTER}" 'Enter
SendKeys "{UP}" 'Up arrow
SendKeys "{CAPSLOCK}" 'Caps lock
SendKeys "+F2" 'Shift + F2
SendKeys "^C" 'Ctrl + C
SendKeys "%F4" 'Alt + F4
Sendkeys has an optional argument which can be set to True or omitted and default
to False. When set to True, any code which runs as a result of the keystrokes is
processed before moving on to the next line of code after the SendKeys statement.
This could be useful if SendKeys actually changed some program data which would
be needed on the next line(s) of code.
SendKeys "{F1}", True

Page 10: Handling Mouse Actions


View Page Comments Leave a Comment

The main purposes of the MouseDown and MouseUp events is to distinguish between
a left click and right click (and middle click if the user has such a mouse), and to
handle drag and drop operations. The Click event cannot do this for you.

The built-in Button argument holds a value corresponding to which button was
clicked. The shift argument holds a value corresponding to the state of the Ctrl, Alt,
and Shift keys. See Page 9: Handling Keyboard Input. The X and Y arguments will
tell you the exact coordinates of where the user clicked.

Private Sub Form_MouseDown(Button As Integer, _


Shift As Integer, X As Single, Y As Single)

If Button = vbLeftButton Then


'Left button was clicked
MsgBox "You clicked the left button at coordinates " & X! & ", " & Y!
ElseIf Button = vbRightButton Then
'Right button was clicked
Else
'Middle button was clicked
End If

End Sub

29
Forms and controls will have their own set of coordinates, so you can't write code for
a control's MouseDown event and expect to get the Form's coordinates unless you
factor in the Left and Top values of the control being clicked.
Private Sub lstMain_MouseDown(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
Msgbox "You clicked coordinates " & _
X! + lstMain.Left & ", " & Y! + lstMain.Top
End Sub
The MouseMove event is triggered constantly as the user moves the mouse. This
event also has Button and Shift arguments, but you're better off using MouseDown
or MouseUp to deal with that. To get an idea of what the MouseMove event is doing,
try this short exercise:

Draw a Label on a blank Form, name it lblXY and add this code:

Private Sub Form_MouseMove(Button As Integer, _


Shift As Integer, X As Single, Y As Single)
lblXY.Caption = X! & ", " & Y!
End Sub
Now run the project, move the mouse and watch the event in action.
You may have seen that aggravating program which wants you to click a button, but
moves the button away once you put your mouse over it. This is very easy to do with
the MouseMove event. Add a command button named cmdClickMe to a Form named
frmMain and add this code.
Private Sub cmdClickMe_MouseMove(Button As Integer, _
Shift As Integer, X As Single, Y As Single)
cmdClickMe.Left = Rnd * (frmMain.Width - cmdClickMe.Width)
cmdClickMe.Top = Rnd * (frmMain.Height - cmdClickMe.Height)
End Sub
Rnd is a VB function which can help you generate random numbers.
One flaw to Visual Basic is the absence of a MouseOver event, and event which fires
just once when the user puts their mouse over the object, and won't fire again
unless the mouse is moved off and then back over. Using a little logic and a little
code, we can get around this. The example below (which assumes a Form named
frmMain and a command button named cmdOK will create MouseOver functionality
for a Form and one control, you'll have to expand it some to make it work with
several controls.
(general)(declarations)
Dim MouseOverfrmMain As Boolean
Dim MouseOvercmdOK As Boolean

Private Sub Form_MouseMove(Button As Integer, _


Shift As Integer, X As Single, Y As Single)
If MouseOverfrmMain Then Exit Sub

Code for MouseOver goes here, could be a general procedure named MouseOver

MouseOverfrmMain = True
MouseOvercmdOk = False
End Sub

Private Sub cmdOK_MouseMove(Button As Integer, _


Shift As Integer, X As Single, Y As Single)
If MouseOvercmdOK Then Exit Sub

Code for MouseOver goes here

MouseOvercmdOK = True
MouseOverfrmMain = False
End Sub

30
Page 11: Focus, Focus
View Page Comments Leave a Comment

Focus refers to which Form is currently active and also to which control on that Form
is currently active. Only one Form can be the ActiveForm and only one control can be
the ActiveControl. A Form can become active by the code you write or the user can
switch to a loaded Form and make it active by clicking it's title bar or restoring it
from a minimized state.

When a Form becomes active, the control which has a TabIndex of 0 automatically
receives the focus and becomes active, however, not all controls will qualify to
receive focus. Labels, Images, Frames, Shapes, and any control which isn't Enabled
cannot receive focus and will be skipped over in the Tab order.

By default, the Tab order is the order in which the controls were drawn on the Form.
If necessary, rearrange the Tab order by rearranging the TabIndex property of each
control. If you want to purposely skip a control in the Tab order, set it's TabStop
property to False. For instance, a PictureBox can receive focus but you may not want
it to be the ActiveControl.

Controls have different visual ways to indicate that they have the focus. For instance,
CommandButtons, OptionButtons, and CheckBoxes will show focus lines around their
Caption, TextBoxes will show a blinking caret, and ComboBoxes will highlight their
text. Keep these focus lines in mind when you draw CommandButtons; these lines
might interfere with the Caption on smaller buttons.

Controls can get the focus when the user tabs to it or directly clicks on it, or you can
force the control to have the focus with code. This is very easy to do with the
SetFocus method.

cmdOK.SetFocus

Write code for the changing state of the ActiveControl in the GotFocus and
LostFocus events. You might want to change the appearance of a control in the
GotFocus event or you could inspect the contents of the control in the LostFocus
event.

Forms have GotFocus and LostFocus events, but normally you won't use these
events because they won't trigger unless the Form has no enabled controls. Use the
Form's Activate and Deactivate events instead. Also, avoid the SetFocus method of a
Form; use the Show and Hide methods as necessary.

Page 12: Data Types


View Page Comments Leave a Comment

31
You need to be aware of the data types you're using. If a computer is expecting a
number and you give it a word, then you're going to have problems. Here is a list of
the most often used data types:

Boolean

The Boolean data type can only have one of two values. True or False. You might
say, well, True and False are not values, but to Visual Basic, the False keyword
represents 0 and the True keyword represents non-zero. Whenever you see or type
False or True in a code window, picture 0 and 1. For example, the Locked property of
a Text Box is a Boolean property. It cannot take any other value, as evidenced by
the Properties Window. Boolean can also be described as an on/off switch.

String

The String data type is text. When dealing with literal strings, we surround the text
in quotes. However, an object may have a String type property; it is not necessary
to put quotes around the property. For instance: txtInput.Text can refer to a string
stored in the property. Strings can be variable or fixed length. Variable-length strings
can hold up to 2 billion characters while fixed length strings are limited to about
65,000 characters.

Byte

Bytes are whole numbers between 0 and 255. This data type is useful for
represented one byte of data. You'll use Integers for most situations.

Integer

Integers are whole numbers between -32,768 and 32,767. This encompasses most
of the numbers you'll ever need. If you feel you may need a number which goes out
of this range, then consider the Long.

Long

Sometimes referred to as the Long Integer. This data type can handle whole
numbers from -2,147,483,648 to 2,147,483,647, but uses 4 bytes of memory
compared to the 2 bytes used by the Integer.

Single

You'll need a Single for numbers which will require a decimal. The Single can handle
negative values from -3.402823E38 to -1.401298E-45 and positive values from
1.401298E-45 to 3.402823E38. Or in other words, up to
340,282,300,000,000,000,000,000,000,000,000,000,000. Need a bigger number
than that? Try the Double.

Double

The Double requires 8 bytes compared to the Single's 4 bytes. But if for whatever
reason you'd need a data type that can handle negative values from

32
-1.79769313486232D308 to -4.94065645841247D-324 and positive values from
4.94065645841247D-324 to 1.79769313486232D308, (I'm not writing that one
out!) then by all means use a Double.

Currency

The Currency data type is obviously used for monetary values. It takes 8 bytes of
memory and can handle numbers from -922,337,203,685,477.5808 to
922,337,203,685,477.5807. This data type has a fixed(4 places) decimal point.

Date

The Date data type is numeric or string data that is capable of being converted to a
valid date.

See Page 32: Dates and Time

Variant

The Variant data type handles all types of data and converts between them
automatically. So why not just use Variants all the time? Because you can speed up
your code and use less memory by using specific data types. There are some cases,
however, where Variants are necessary.

Converting one data type to another

You'll find many cases where you'll need to convert a String to a number, a number
to a String, a Single to a Long, a String to a Date, etc. There are functions available
which convert any String or number to a particular data type. These conversions
won't be error free, for instance, every number and String doesn't qualify as a Date,
and some numbers are too large to be an Integer.

'Convert to Boolean, (numbers only)


bResult = CBool(number)

'Convert to Integer. If the number being converted


has a decimal, it is rounded, with one quirk: if the
decimal is .5, the function will round to the nearest
even number. Use the Format function for better rounding
iResult% = CInt(number)

'Convert to Long. See notes on CInt


lResult& = Clng(number)

'Convert to String. Will convert just about any expression to text:


Boolean gets converted to the string "True", or "False". Won't convert
expressions which cause errors, for example a division by zero.
Also can't convert a Null value.
sResult$ = CStr(expression)

'Convert to Single
sngResult! = CSng(number)

'Convert to Double
dblResult# = CDbl(number)

'Convert to Byte
Result = CByte(number)

33
'Convert to Currency
cResult@ = CCur(number)

'Convert to Date (CVDate function is outdated)


dResult = CDate(expression)

'Convert to Variant
vResult = CVar(expression)
Create your own data types

Encapsulate your related variables into User Defined Types (UDT) with a Type
statement placed in the general declarations section. Here's a sample UDT which
might store information about this tutorial.

Type VBTutorial
iNumPages As Integer
sURL As String
sAuthorName As String
bDownloadable As Boolean
End Type
To use a UDT, a variable is created As type name.
Dim myPages As VBTutorial
This variable then acts somewhat like an object, where the individual elements are
accessed like properties and use of the With statement is allowed.
With myPages
.iNumPages = 46
.sURL = "http://www.officecomputertraining.com/vbtutorial/knowcode.htm
.sAuthorName = "James S. Laferriere"
.bDownloadable = False
End With
Note: I have received some complaints that the tutorial should be zipped up for off-
line reading. I hear these cries and will comply in the near future.

When type statements are used in Form or Class modules, they are forced to be
Private. Insert the Private keyword before the Type statement if you have no use for
the UDT outside the module it resides in.

Private Type VBTutorial


iNumPages As Integer
sURL As String
sAuthorName As String
bDownloadable As Boolean
End Type

Page 13: Variables and Constants


View Page Comments Leave a Comment

A variable is temporary storage space for numbers, text, and objects. Variables are
constantly being created and destroyed and will not hold any values after your
program has ended. If you want to save the values of variables or other data, see
Page 24: Input/Output for Text Files. Before allocating storage space for a variable,
decide what the variable's lifetime will be, or in other words, which procedures and
which modules should have access to the variable's value.

• Procedure-level variables are created with a Dim statement placed right in


the procedure where it's going to be used. The value of a procedure level

34
variable cannot be accessed outside it's procedure. When the procedure
finishes (End Sub or End Function), the variable is destroyed and memory
allocated to the variable is released.
• Module-level variables are created with a Private statement in the general
declarations section of a Form or code module. The value of the module level
variable is available to every procedure in that module. Memory allocated to
the module-level variable is not destroyed until the module is Unloaded.
• Global variables are created with a Public statement in the general
declarations section of a Form or code module. The value of a Global variable
is available to any procedure, in any Form or code module. Memory allocated
to a Global variable is not released until your program shuts down.

It would certainly be easier to make every variable Global. You wouldn't have to
think twice about it's availability at any given time. But sometimes, every byte of
memory counts, so don't give your variables any more "life" than they actually
require.

You'll give your variable a name, and also a data type depending on what kind of
value it will have.

Variable Name Rules

• Maximum 255 characters


• Must begin with a letter
• Do not use spaces

35
• Do not use VB keywords
• No punctuation except the underscore (_) character

'No!
Dim 5Days As Date
Private my Number As Integer
Public string As String

Code modules are files with a .BAS extension which hold the general procedures
which YOU write. Many times, we refer to these as standard modules, or just
modules. A Form is also a module, but referred to as a Form module, or sometimes
an object module.

To add a code module to your project, select Add Module from the Project menu or
click on the Project Explorer button on the toolbar, then right click anywhere on the
Project Explorer window and select Module from the Add submenu.

Static variables

Only Procedure-level variables can be declared Static. This means the variable is not
destroyed and will retain it's value between multiple calls to the procedure. This
might not sound any different than a module-level variable which is used in more
than one procedure while the Static variable is associated with only one.

Private Sub cmdClickMe_Click()

'Keep track of button clicks


Static iCount As Integer
iCount% = iCount% + 1
MsgBox "I've been clicked " & iCount% & " times."

End Sub
Constants

Constants are special variables which have one permanent value.

Const TITLE = "VB Tutorial"

Now, with this Constant declaration, we can use the word TITLE in place of "VB
Tutorial" at any time. Notice the name of the constant is in caps. This is a common
practice which enables programmers to recognize their constants at a glance. At
first, it might seem silly to use constants when you can just use the actual value, but
constants make code easier to write and also easier to read.

Constants can also be Public.

Public Const TITLE = "VB Tutorial"

Here, TITLE can be used throughout your project. Without the Public keyword, TITLE
can only be used in the module or procedure where it is written. Also, Public
constants are not allowed in Form modules

Prefixes and Type Declaration Characters

36
To always be aware of the data types you're using, it's a good idea to name your
variables with an appropriate prefix. There are a few different styles, but I
recommend a single letter prefix in the Dim statement and a type declaration
character suffix when actually using the variable.

For example, the type declaration character for a string is $.

Use Dim sName As String and then when using the variable in code, use sName$

Type Declaration Characters

• $ (String) 'prefix s, str


• % (Integer) 'prefix i, int
• & (Long) 'prefix l
• ! (Single) 'prefix sin
• # (Double) 'prefix d
• @ (Currency) 'prefix c, cur

There is no character for Boolean, but a prefix of b, or bool is suggested. As a matter


of fact, all of the above on prefixes and characters is suggested. You are not
obligated to use them, but you are obligated to follow the variable naming rules.

Set your copy of VB to require variable declaration!

Page 14: Using Pictures


View Page Comments Leave a Comment

Pictures will really spruce up your application. You can embed pictures right into your
program by simply setting the Picture property of the Form, Image, PictureBox,
Command Button, CheckBox, or Option Button control. When you do this, the picture
is automatically stored in a file with the same name and location of the Form, but
with an .FRX extension. For instance, if you set the Picture property of a Form named
frmStartUp, you'll see that VB creates a file named frmStartUp.frx after the project
has been saved. This .FRX file can hold multiple pictures if you've used additional
pictures on the same Form. When you compile the program, the pictures become
part of the executable (.EXE) file, therefore, using this method can increase the size
of the .EXE dramatically depending on the size of the pictures you've used.

A more common method is to keep your images separate and load them into the
program when necessary. The easiest way to do this is copy the picture file to your
project's PATH (wherever the .vbp is located), and then use the LoadPicture
function. (App.Path will return this PATH).

Personally, I keep all my projects in their own sub-directories of a desktop folder


named VBProj. For each new project I start, I create a new sub-directory of VBProj
and name it after the project name. If I need pictures for the project, I create a sub-
directory of the project named "images". Before I attempt to use any picture file, I
copy it to the images directory and then load it like this:

37
Private Sub Form_Load( )
Picture = LoadPicture(App.Path & "\images\BackDrop.GIF")
End Sub

You don't have to use my method of organizing projects, just remember that
App.Path will return the location of the project file (.VBP).

Deleting a picture from the Picture property at run-time is as simple as using an


empty string with LoadPicture.
frmStartUp.Picture = LoadPicture("")

To delete a picture at design-time, go to the Properties Window, highlight the word


between the parentheses - probably (Bitmap) - and press the delete key.

If you need to use the same picture in more than one place, you'll only need to use
LoadPicture once. To access a picture when it's already in the program, just assign
one Picture property to equal another.
imgFace.Picture = frmStartUp.Picture

This also works with other picture type properties like Icon, DragIcon, MouseIcon,
DownPicture, and DisabledPicture.

imgLogo.Picture = frmStartUp.Icon
cmdOK.MouseIcon = imgLogo.Picture
optHide.DownPicture = optFont.DisabledPicture

Unless you're an artist, pictures will not always be the right size for your project. The
Image control can resize it's pictures to fit the size you've drawn the control, but
most of the time the picture becomes distorted. An exception to this is Windows
Metafiles (.WMF) which stretch very nicely.

Use a graphics program like Paint Shop Pro to resize pictures to fit you needs.

Command Buttons, Option Buttons, and CheckBoxes can also have pictures as well
as a Caption. The Caption can only appear below the picture. These three controls
can actually have three different pictures for different situations.

• Set the Picture property to display a picture when the Button or CheckBox is
Enabled and not pressed.
• Set the DisabledPicture property to display a picture when the button's
Enabled property is False.
• Set the DownPicture property to display a different picture when the button is
pressed in. Option Buttons and CheckBoxes are pressed in when their Value
property is True, or the condition is "on". Command Buttons are only pressed
in for a split second, but with the right combination of Picture and
DownPicture you might be able to create a nice effect.

Available sample: Scrolling a Picture (29K)


How to use Horizontal and Vertical scroll bars to scroll a large picture.

38
Page 15: Menus
View Page Comments Leave a Comment

Menus give your application a professional look. Everything your application does
should be accessible through the menus. In order to make your menus user-friendly,
you should adhere to some simple guidelines, like using File, Edit, View, Options, and
Help as top-level menu titles. Limit your sub-menus to 2 per menu, to make
navigation easier. Some users, like myself, prefer keyboard shortcuts, so be sure to
include them.

Using the Menu Editor

To bring up the Menu Editor, make sure the Form designer is the active window and
click Tools/Menu Editor or press Ctrl+E. The caption and the name fields are
required.

Caption: This is what the user will see. Make the caption as short and as descriptive
of its action as you possibly can. Use proper case.

Name: Menu items are controls and must have a name, the same as any other
control, but the Menu Editor does not supply a default, so use the mnu prefix.
Example: mnuFile, mnuEdit

You may want to layout the entire menu on paper before beginning with the Menu
Editor. In a carefully planned menu, you'll type the Caption, then the Name, the Next
Button and the left or right arrow to indent or outdent the next menu item within the
structure.

Menu items which are not indented are menu titles. Menu items indented once below
these titles are menus which can contain up to 4 sub-menus. Use the arrow buttons
and the Insert and Delete buttons to move about the menu structure and make
adjustments.

Access and Shortcut Keys

Look at the menu of your browser. You'll see menu titles with one letter underlined.
This enables users to press ALT, then the underlined letter in the menu title, thereby
displaying that menu. Furthermore, the user can type the underlined letter in one of
the menu items in order to perform that action. Try this if you're not familiar with
using the access keys.

I use access keys for cut, copy, and pasting. I find it quicker and easier to type ALT,
E, C, and ALT, E, P, rather than grabbing my mouse and clicking the menus. You
may not agree, and that's fine, it's all a matter of personal preference, so design
your menus with all kinds of users in mind.

To actually make access keys, you type your caption with an ampersand(&) just
before the designated letter. For example, the File title's caption is &File. Most often,
the first letter of the caption is the access key, but you may need to use a different

39
letter to avoid conflict with another menu item. Each menu title is separate when
considering access keys, by that I mean you can use the letter C more than once as
long as those menu items are under different titles.

Assign shortcut keys in the Menu Editor's Shortcut field. There are some standard
Windows shortcut keys to consider like Ctrl+X, Ctrl+C, and Ctrl+V for cut, copy and
pasting actions. Don't defer from the norm, you may confuse your users. My
preference is to use the functions keys (F1 to F12, excluding F10) as shortcuts
because it's just one key.

Separator bar

Putting a separator bar in the menu is simple, just use a dash for the menu item's
Caption. The separator bar is actually still a menu control, so it must have a name. I
like to use "Junk1", "Junk2", etc.

Checked, Enabled, and Visible

Like other controls, menu items can be grayed out by setting Enabled to False, or
completely removed by setting Visible to False. (At least one sub-menu must be
visible.) You can also display a check mark next to a menu item indicating that an
on/off condition is on.

Index

Menu items can be part of a control array.

WindowList

A menu can be a list of MDI child windows. The list is handled automatically by the
MDI Form.

NegotiatePosition

Used with object linking and embedding (OLE)

PopupMenu method

Your menus don't have to be located in the menu bar. With the PopupMenu method,
you can make a menu appear anywhere on the form. You can also make the menu
appear upon right-clicking the mouse. First, decide if you want the menu to only be
visible when it pops up, or visible in the menu bar as well. If you don't want the
menu in the menu bar, just set the menu title's Visible property to False. Let's say
you want a menu to pop up when the user right-clicks or left-clicks on the form.

Private Sub Form_Click( )


PopupMenu mnuPopup, vbPopupMenuRightButton
End Sub

To make the menu respond to only a right-click, use the MouseDown event instead
and inspect the Button argument.

40
By default, the pop-up menu will appear below and to the right of the mouse cursor,
but this can be controlled by specifying alignment constants.

'Menu appears below and centered on the mouse cursor


PopupMenu mnuPopup, vbPopupMenuCenterAlign

'Menu appears below and to the left of the mouse cursor


PopupMenu mnuPopup, vbPopupMenuRightAlign

'Menu is centered and responds to right-clicks


'Use the Or operator to combine settings
PopupMenu mnuPopup, vbPopupMenuCenterAlign Or vbPopupMenuRightButton

The menu doesn't have to pop up relative to the position of the mouse. Supply X and
Y values to make the menu popup anywhere on the form. Just remember, if you use
the default settings (vbPopupMenuLeftAlign and vbPopupMenuLeftButton), you must
still include the comma separator.

'Menu appears centered on the form


'regardless of where the mouse click came from
PopupMenu mnuFile, , frmMain.Width / 2, frmMain.Height / 2

One more thing you can do with popup menus is make one of the menu items bold-
faced. You'd probably do this to indicate to the user that a particular selection is
appropriate at this time. Supply the name of the menu item as the last argument of
the PopupMenu method.

PopupMenu mnuFile, , frmMain.Width / 2, frmMain.Height / 2, mnuEdit

Page 16: Dialog Boxes


View Page Comments Leave a Comment

Dialog Boxes are used to get information from the user or simply to display a
message. There are several kinds of dialog boxes, but only VB's built-in InputBox
and MsgBox will be discussed on this page.

MsgBox Statement

This is the simplest use of the MsgBox which just displays a message to the user.

MsgBox "Hello"

Text1.Text = "Bill Gates"


Msgbox Text1.Text & " is not a valid name"

Either of the above MsgBox statements present dialog boxes (which are actually just
small forms) with an OK button. By default the name of your project will be in the
title bar. The second example uses the ampersand to concatenate a message from a
TextBox and a literal string. You can change the appearance of the MsgBox with
some optional arguments.

MsgBox "Hello", vbExclamation, "Greeting"

41
The second argument is a VB built-in constant which will add an exclamation icon to
the MsgBox. The third argument is the string you want displayed in the title bar.
Note the different sound you'll get with the exclamation icon.

There are four VB constants that you can use for the icon argument.

• vbCritical (warns the user of a serious problem)


• vbExlamation (a non-critical warning message)
• vbQuestion (use when the MsgBox is asking a question)
• vbInformation (simple informative messages)

MsgBox Function

You can ask the user a question and display various buttons in the MsgBox Function,
which has the same syntax as the MsgBox statement, but the arguments are
enclosed in quotes and the Function needs a place (variable or property of a control)
to store the VB constant which corresponds to which of the buttons was clicked.

Dim iResponse As Integer


iResponse% = MsgBox("Are you over 18?", vbQuestion + vbYesNo, "Verify Age")

Note the constant vbYesNo, (which displays Yes and No buttons) is added to the
icon constant. Adding constants creates a unique value which the MsgBox interprets.
The iResponse variable holds a value (again, a VB constant, there's no need to
memorize the actual values) which you can use in code to perform the next task
based on what button the user clicked.

If iResponse% = vbYes Then


MsgBox "You are allowed to vote", vbInformation, "Election"
Else
MsgBox "Sorry, you're underage!", vbExclamation, "Election"
End If

Here are the constants for the different button combinations available.

• vbOkOnly
• vbOkCancel
• vbRetryCancel
• vbYesNo
• vbYesNoCancel
• vbAbortRetryIgnore

InputBox Function

If you need some text from the user, rather than simply a yes or no question, use
the InputBox. Like the MsgBox Function, the InputBox needs a place to store the
user's input, in this case a string.

Dim sResponse As String


sResponse$ = InputBox("What city do you live in?", , "San Diego", 500, 500)

Let's look at the InputBox arguments. The first is the same as the MsgBox, the
prompt. The second is the title, but in the above example, we're electing to use the

42
default by not specifying anything. If you decide against using one a function's
optional arguments, you must still use the comma separator. The third argument,
the default text, is also optional. The last two numbers are X and Y (Left and Top)
values, which you can use if you want to place the InputBox in a certain position on
the screen. (The MsgBox does not have this capability.)

The InputBox is displayed with OK and Cancel buttons, you can't change that. If the
user clicks Cancel, an empty string is returned. If the user clears the InputBox and
clicks OK, this also returns an empty string. You cannot differentiate between these
two user actions which produce the same result. If this is a problem for your
situation, you will have to create your own custom dialog box. Create a Form with
the BorderStyle property set to 3 - Fixed Dialog and draw any controls which are
needed. Show the Form modally and obtain the user's input from properties of the
dialog's controls. Example

Page 17: Working With Numbers


View Page Comments Leave a Comment

Manipulating numbers is a large part of programming. If you're one of those people


who is not good with numbers, then I might suggest that programming is not for
you.

Basic arithmetic with VB can be done with regular numbers, variables, constants,
numeric properties, and even numeric strings.

Operators

Basic math operations use the following symbols:

Addition (+) and Subtraction (-)


'Get your new checking account balance
Dim cOldBalance As Currency, cNewBalance As Currency
Dim cDeposit As Currency, cWithdrawal As Currency
Dim cInterest As Currency, cChecks As Currency
cOldBalance@ = 1243.32
'Paycheck deposited
cDeposit@ = 2343.12
'Total of checks written
cWithDrawal@ = 2119.76
'Interest Earned
cInterest@ = 3.32
cNewBalance@ = cOldBalance@ + _
cDeposit@ + cInterest@ - cChecks@
Multiplication (*) and Division (/)
'Temperature conversion
On Error GoTo errorhandler

Dim iDegreesF As Integer, iDegreesC As Integer


iDegreesC% = InputBox("Enter degrees in Celsius")
iDegreesF% = iDegreesC% * 1.8 + 32
MsgBox iDegreesC% & " degrees Celsius converted to Farenheit is " & iDegreesF%

iDegreesF% = InputBox("Enter degrees in Farenheit")


iDegreesC% = (iDegreesF% - 32) / 1.8

43
MsgBox iDegreesF% & " degrees Farenheit converted to Celsius is " & iDegreesC%

Exit Sub

errorhandler:
MsgBox "Invalid input", vbInformation

Temperature conversion from Farenheit to Celsius requires a slightly different


formula. 32 must be subtracted from Farenheit before being divided by 1.8. VB
calculates formulas with multiple operations in a definite order. You can bring certain
parts of a formula to the top of this order by using parentheses. Here is the default
operator precedence.

1.) Exponentiation (^)


2.) Negation (-)
3.) Multiplication, Division (*, /)
4.) Integer Division (\)
5.) Modulus (Mod)
6.) Addition, Subtraction (+, -)

Dim A As Integer, B As Integer, C As Integer


Dim iResult As Integer
A = 8: B = 6: C = 4
iResult% = A * B + C 'iResult = 52
iResult% = A * (B + C) 'iResult = 80

The caret (^) is used to raise a number to certain power. (Exponentiation)

'Get square inches from square yards


On Error Resume Next

Dim iSqInches As Integer


Dim iSqYards As Integer
Const FEET_PER_YARD = 3
Const INCHES_PER_FOOT = 12

iSqYards% = InputBox("Enter number of square yards")


iSqInches% = FEET_PER_YARD ^ 2 * _
INCHES_PER_FOOT ^ 2 * iSqYards%
MsgBox "There are " & iSqInches% & _
" square inches in " & iSqYards% & " square yard(s)."

The backslash is used to divide two numbers and drop the remainder (Integer
Division).

Dim iElevatorLimit As Integer


Dim iBodyWeight As Integer
Dim iPersons As Integer

iElevatorLimit% = 3500
iBodyWeight% = 180

'How many 180 pound persons


'are allowed on this elevator?
iPersons% = iElevatorLimit% \ iBodyWeight%

To get the remainder of a division, use the Mod operator (Modulus). Here's a general
procedure which returns True if a number is odd, False if it's even.

Public Function IsOdd(ByVal iNumberToCheck As Integer) As Boolean

44
IsOdd = IIf(iNumberToCheck% Mod 2 = 1, True, False)

End Function

A number can be switched from positive to negative or vice versa by putting a minus
sign (-) in front of it (Negation)

'Randomly move an image left or right


Dim iMoveAmount As Integer, iRandom As Integer
iMoveAmount% = 100
Randomize
iRandom% = Int(Rnd * 2)
imgCDROM.Move imgCDROM.Left + _
IIf(iRandom% = 0, iMoveAmount%, -iMoveAmount%)
Built-in Functions

You should be able to find a VB function for all your numeric needs.

Round function

Bill finally added a simple function for rounding numbers in VB 6.0. Previously,
coders had relied on the Format function for rounding.

'iResult% = 0.667
iResult% = Round(2 / 3, 3)
The second argument is the number of places to round to...When rounding to a
whole number, use 0 or just leave it out, it's an optional argument. One thing that
this function cannot do is drop a leading zero. For that task, you'll have to use the
old standby Format function with a format string of ".000".
Int and Fix functions

Use Int to drop the fractional part of a positive number.

'iResult% = 5
iResult% = Int(5.5)
Use Fix to drop the fractional part of a negative number.
'iResult% = 12
iResult% = Fix(-12.9)

Using Int on a negative number still works the same way, returning the next lowest
whole number, but that might not be the result you want.

'iResult% = 24
iResult% = Int(-23.6)
Abs function

Abs (absolute value) is useful for getting the difference between two numbers
without worrying about which is larger. In other words, the function converts
negative values to positive. Positive numbers are unaffected by the function.

'Find the left-right distance


'between two images.
Dim iDistance As Integer
iDistance% = Abs(imgPlayer.left - imgExit.left)
Val function

45
Val takes a numeric string and converts it to the appropriate numeric data type,
enabling you to use the string in calculations.

'Increment a Label by 1
lblTotal.Caption = Val(lblTotal.Caption) + 1
Rnd function

A great function which generates a random decimal between 0 and 1. That by itself
may not be useful, so you'll have to perform calculations on the function.

Randomize
iResult% = Int(Rnd * 100) + 1

This formula produces a random whole number between 1 and 100. Precede the
formula with the Randomize statement to get a good mix of random numbers.
Otherwise, you might end up with the same numbers over and over again.

Sgn function

This function will determine the sign of a number and returns -1 if negative, 0 if
zero, and 1 if positive.

'You might replace a line like this...


If iNumber% <> 0 Then
'...with this
If Sgn(iNumber%) Then
Note: Using functions like Sgn is more efficient than comparing for inequality (<>)
Sqr function

Sqr gets the square root of a number. The function returns a Double.

'dResult# = 4.795831523313
dResult# = Sqr(23)

This number is rounded off to 12 decimal places because I can't write the entire
number here. This is when the Page 18: Format Function comes in handy.

Hex and Oct functions

You must understand hexadecimal and octal notation to use these functions. This will
be discussed in a future page.

Accounting Functions

If you're the accounting type, VB has taken care of you with numerous functions for
loans, depreciation, interest, etc. Here is a list of accounting functions and their
purpose. However, I'm not going to explain bookkeeping at this time.

• FV: Future value of an annuity


• PV: Present value of an annuity
• IPmt: Interest Payments
• IRR: Internal Rate of Return
• MIRR: Modified Internal Rate of Return

46
• NPer: Number of Periods
• NPV: Net Present Value
• Pmt: Payment of an annuity
• PPmt: Principal Payment of an annuity
• DDB: Double-declining balance method of depreciation
• SLN: Straight Line method of depreciation
• SYD: Sum-of-the-years digits method of depreciation
• Rate: Interest Rate

If you're into geometry, you're all set there too. From the list of VB functions below,
you can make any geometric calculation that exists. (Assuming you're Albert
Einstein).

• Atn (Arctangent)
• Tan (Tangent)
• Sin (Sine)
• Cos (Cosine)
• Log (Logarithm)
• Exp (Anti-Logarithm)

Page 18: Formatting Functions


View Page Comments Leave a Comment

Bill has added 4 new formattings function in VB 6.0. Read about them here

The Format function will format a number with any amount of decimal places,
leading zeros, trailing zeros, and round it at the same time. Another big use of
Format is to convert numbers into date and time expressions. The function takes two
arguments, the number to format and a string which indicates instructions on what
kind of formatting is to be done. These instructions can be built-in named formats or
any combination of special characters. The only way to really learn what this function
can do, is to look at a few examples and experiment on your own.

Formatting numbers with named formats


Expression Result
Format(35988.3708, "general number") 35988.3708
Format(35988.3708, "currency") $35,988.37
Format(-35988.3708, "currency") ($35,988.37)
Format(35988.3708, "fixed") 35988.37
Format(1, "fixed") 1.00
Format(35988.3708, "standard") 35,988.37
Format(1, "standard") 1.00
Format(0.35988, "percent") 35.99%
Format(0, "Yes/No") No
Format(0.35988, "Yes/No") Yes

47
Format(0, "True/False") False
Format(342, "True/False") True
Format(0, "On/Off") Off
Format(-1, "On/Off") On
Formatting numbers with special characters
Expression Result
Format(35988.3708, "00000.0") 35988.4
Format(35988.3708, "0000000.0") 0035988.4
Format(35988.3708, "00,000.00000") 35,988.37080)
Format(6.07, "0.###") 6.07
Format(6.07, "0.000##") 6.070
Format(143879, "#,###,###.00") 143,879.00

A "0" placeholder in your format string will force a character to appear in that
position regardless of the value, or in other words, give you leading and/or trailing
zeros. This is very useful for formatting numbers to a certain number of decimal
places, for instance a baseball batting average.

Dim vAverage As Variant


Dim iAtBats As Integer
Dim iHits As Integer
iAtBats% = 128: iHits% = 37

'Returns .289
vAverage = Format(iHits% / iAtBats%, ".000")

A "#" placeholder will only display a number in that position if it isn't a leading or
trailing zero. We might change the format string of a baseball average to...

vAverage = Format(iHits% / iAtBats%, "#.000")

...to account for the rare occasion when a player is batting 1.000

Put commas and decimal points in the appropriate places within the format string.

To display any literal character(s) within the formatted output, precede the
character(s) with a backslash within the format string. The backslash is not
displayed.

Formatting numbers with embedded characters


Expression Result
Format(45, "\[00\]") [45]
Format(642, "\£000.00") £642.00
Format(99, "00\¢") 99¢
Format(8, "#0\).") 8).
The Format function can even make If-Then-Else type decisions. You can send up to
four different format strings, separated by semi-colons, to the function, where your
number will be formatted according what kind of value it is- positive, negative, zero,
or null. You cannot use named formats with this method.

48
When using a two-section format string, the first section applies to positive values
(including zero) and the second section to negative values. In a three-section format
string, the third section is for zero values, thereby releasing the first section from
handling zero. With a four-section format string, the fourth section is for null values.

iResult% = Format(iNumber%, "##.00;\(##.00\);0;\E\r\r\o\r")


Formatting dates and time

The Format function can also be used on Date/Time serial numbers to extract and
format all kinds of information ranging from seconds to days to years. There are a
handful of named format strings and constructing your own strings are not out of the
question. No one except Bill usually memorizes the formatting characters, so I'll just
dump a list of them on you here.

Formatting numbers as date/time expressions


Expression Result
Format(36715.5784, "general date") 7/8/00 1:52:54 PM
Format(36715.5784, "short date") 7/8/00
Format(36715.5784, "medium date") 08-Jul-00
Format(36715.5784, "long date") Saturday, July 08, 2000
Format(36715.5784, "short time") 13:52
Format(36715.5784, "medium time") 01:52 PM
Format(36715.5784, "long time") 1:52:54 PM
Format(36715.5784, "c") 7/8/00 1:52:54 PM
Format(36715.5784, "d") 8
Format(36715.5784, "dd") 08
Format(36715.5784, "ddd") Sat
Format(36715.5784, "dddd") Saturday
Format(36715.5784, "ddddd") 7/8/00
Format(36715.5784, "dddddd") Saturday, July 08, 2000
Format(36715.5784, "w") 7
Format(36715.5784, "ww") 28
Format(36715.5784, "m") 7
Format(36715.5784, "mm") 07
Format(36715.5784, "mmm") Jul
Format(36715.5784, "mmmm") July
Format(36715.5784, "q") 3
Format(36715.5784, "y") 190
Format(36715.5784, "yy") 00
Format(36715.5784, "yyyy") 2000
Format(36715.5784, "h") 13
Format(36715.5784, "hh") 13
Format(36715.5784, "n") 52
Format(36715.5784, "nn") 52
Format(36715.5784, "s") 54
Format(36715.5784, "ss") 54
Format(36715.5784, "ttttt") 1:52:54 PM
Format(36715.5784, "AM/PM") PM

49
Format(36715.5784, "am/pm") pm
Format(36715.5784, "A/P") P
Format(36715.5784, "a/p") p
Format(36715.5784, "AMPM") PM

Notes: Some of this formatting is duplicated by easier-to-use Date and Time


functions. Results of the named formats (short date, medium date, etc.) may vary
depending on Control Panel Regional Settings.

• Format "w" returns day of week (1 = Sunday, 7 = Saturday)


• Format "ww" returns week of year (1-53)
• Format "y" returns day of year (1-366)
• Format "h" returns hour of day as one or two digits...if necessary
• Format "hh" returns hour of day as two digits...definitely
• Above applies to "n"/"nn", and "s"/"ss" as well
• Format "AMPM" uses settings from WIN.INI [intl] s1159=AM, s2359=PM

Try mixing and matching the format strings


Expression Result
Format(36715.5784, "m-d-yy") 7-8-00
Format(36715.5784, "d-mmmm-y") 8-July-00
Format(36715.5784, "mmmm yyyy") July 2000
Format(36715.5784, "hh:mm a/p") 01:52 p
Format(36715.5784, "m/d/yy h:mm") 7/8/00 13:52
New VB 6.0 formatting functions

FormatCurrency, FormatPercent, FormatNumber

Previously, you would have used code similar to:


result = Format(324.45, "currency")
result = Format(324.45, "percent")
result = Format(324.45, "standard")

These methods would now be considered deprecated in favor of:


result = FormatCurrency(324.45)
result = FormatPercent(324.45, 0)
result = FormatNumber(324.45, 2)

Bill's new formatting functions includes 4 optional arguments for specifying the
number of digits after the decimal, and whether or not to use a leading 0,
parentheses for negative values, or a comma for thousands separator. If you would
just as soon use the settings from the user's Control Panel, ignore the last 4
arguments.

'Use default Control Panel Regional Settings


result = FormatCurrency(number)

'Default digits after decimal, no leading zeros or thousands separator


result = FormatNumber(number, , False, , False)

'One digit after decimal, no parentheses


result = FormatPercent(number, 1, , False)

50
FormatDateTime

This new function works about the same as the regular Format function, but you're
only allowed to use one of 5 constants - vbGeneralDate, vbLongDate, vbShortDate,
vbLongTime, vbShortTime. I don't see this function as any kind of language
improvement.

Page 19: Working with Strings


View Page Comments Leave a Comment
Option Compare Text

A big part of working with strings is comparing one to another to see if they match.
There are two ways to compare strings - case sensitive and case insensitive.

• Case sensitive is a Binary comparison.


The string "VB" is not equal to the string "vb".
• Case insensitive is a Text comparison.
The strings "VB" and "vb" are equal.

By default, VB will use the Binary method to compare strings. This can be changed
for individual modules by placing an Option Compare Text statement in the general
declarations section. Option Compare Binary is also a valid statement, but it's never
necessary to write this out.

LCase and UCase functions

These two functions will convert strings to all lower or all upper case. This might be
useful if you need to compare strings. In the below example, the user is typing their
two-letter state abbreviation into a Text Box.

Private Sub txtState_Change( )


Dim sStAbbr As String
sStAbbr$ = "CA"
If UCase$(txtState.Text) = sStAbbr$ Then
MsgBox "So you live in California? I'm sorry to hear that."
End If
End Sub
Trim, LTrim, and RTrim functions

These functions remove leading (LTrim) or trailing (RTrim) spaces from a string. It
won't affect any spaces between words. Trim simply accomplishes both LTrim and
RTrim. Users may inadvertantly type spaces into a Text Box, and you'd use these
functions to account for that, but the largest use of Trim is with fixed-length strings,
user-defined Types, and Random Access Files.

'sResult$ = "excel"
sResult$ = LCase(Trim(" EXCEL"))

Here, we've used a function as an argument for another function. This is commonly
done and an efficient way to program.

51
Len function

This function gets how many characters long the string is. All characters are counted
including spaces and punctuation.

'iResult% = 17
Dim sAddress As String, iResult As Integer
sAddress$ = "345 St. James St."
iResult% = Len(sAddress$)
Left and Right functions

Used to extract a certain number of characters from the leftmost or rightmost


portion of a string. The function requires two arguments: the original string and the
number of characters to extract.

Dim sFileName As String, sFileTitle As String


Dim sFileExt As String
sFileName$ = "command.com"
'sFileTitle$ = "command"
sFileTitle$ = Left(sFileName$, Len(sFileName$) - 4)
'sFileExt$ = "com"
sFileExt$ = Right(sFileName$, 3)

The Len function is used in the first example as an argument for the Left function to
calculate the number of characters we need without the dot and a 3-letter extension.
The second example is simpler, we're just getting the last three characters of the
string.

Mid function and Mid statement

You'll need the Mid function if you want to extract characters from the middle of a
string. We need three arguments here: The original string, the place to start
extracting, and the number of characters to extract.

The Mid statement not only extracts the characters, but replaces them with the text
you specify. Since this is a statement (not a function), you won't get a result, rather
the action is just completed for you.

Here's a general procedure which uses both the function and the statement to
convert a string to that hideous style that the youngsters are so fond of - embedded
capitalization.

Public Function EveryOtherCap(ByRef sInput As String) As String

Dim i As Integer, sCurrentChar As String


For i% = 1 To Len(sInput$)
sCurrentChar$ = Mid(sInput$, i%, 1) 'function
If i% Mod 2 = 0 Then
Mid(sInput$, i%, 1) = LCase(sCurrentChar$) 'statement
Else
Mid(sInput$, i%, 1) = UCase(sCurrentChar$)
End If
Next i%

EveryOtherCap = sInput$

End Function

52
'Calling the function
Dim myString As String
myString$ = "i want to be vb programmer"
'Converts myString to "I WaNt tO Be vB PrOgRaMmEr"
myString$ = EveryOtherCap(myString$)
InStr function

When I was a rookie and just learning Bill's ways, this was one of the hardest
functions for me to learn. InStr searches for strings within strings. Its first argument
is the position in the string to start searching. The second argument is the string to
search. The third is the string to search for and the last argument is whether or not
you want a case-sensitive search. (0 for yes, 1 for no). The first and last arguments
are not required and for simple searches you will omit them.

InStr is a function and will return the position of the first occurence of the string. If
the string is not found, the function returns 0.

Here's two useful functions which extract the left-most or right-most characters from
a string based on a certain character. If you read the example on the Left and Right
functions, you'll notice this a better approach for disecting a file name because it
accounts for extensions which are not 3 letters.

Public Function RightOfChar(ByVal sInput As String, _


ByVal sChar As String) As String

On Error Resume Next


Dim iPos As Integer
'Get position of character
'First and last arguments omitted
iPos% = InStr(sInput$, sChar$)
RightOfChar = Right(sInput$, Len(sInput) - iPos%)

End Function

Public Function LeftOfChar(ByVal sInput As String, _


ByVal sChar As String) As String

On Error Resume Next


Dim iPos As Integer
iPos% = InStr(sInput$, sChar$)
LeftOfChar = Left(sInput$, iPos% - 1)

End Function

'Using the functions


Dim sFileName As String, sFileExt As String
Dim sFileTitle As String
sFileName$ = "index.aspl"
sFileExt$ = RightOfChar(sFileName$, ".")
sFileTitle$ = LeftOfChar(sFileName$, ".")
InstrRev function (VB 6.0 only)

Bill has solved the main drawback to the InStr function by including the InStrRev
function with VB 6.0. InStrRev finds the last occurence of a string, rather than the
first. You'll most likely use this function to find the last position of a single character
string.

sPath$ = "C:\Program Files\VB98\Setup\1033\setup.exe"

53
Imagine your task is to find the position of the last backslash in this string. If you're
stuck with VB 5.0 or less, you might use a custom function like this:

Function FindLastCharPos(ByVal sInput As String, _


ByVal sChar As String) As Integer

Dim i As Integer

For i% = Len(sInput) To 1 Step -1


If Mid(sInput, i%, 1) = sChar$ Then
FindLastCharPos = i%: Exit For
End If
Next

End Function

'Call the function


Dim iPos As Integer
iPos = FindLastCharPos(sPath$, "\")
Here's the much easier way with InStrRev:
Dim iPos As Integer
iPos% = InStrRev(sPath$, "\")

InStrRev can use the same start position and case-sensitive arguments as InStr.

Replace function (VB 6.0 only)

Before VB 6.0, "Find and Replace" was not a simple task. You might have tried the
Mid statement, but you found out that you had to write extra code to account for
different lengths of the find string and the replace string. Thank you Bill, I really
needed this new function.

txtCode.Text = Replace(txtCode.Text, "VB 5.0", "VB 6.0")

If you're not using VB 6.0, try this function

You can see the required arguments above, the string to perform the operation on,
followed by the "find" string, and then the "replace" string. You can set a fourth
argument to start the replacements at a certain position while deleting the whatever
is before this position.
'Return string after insertion point with replacements
txtCode.Text = Replace(txtCode.Text, "VB 5.0", "VB 6.0", txtCode.SelStart + 1)

If you use this start argument, you'll be disappointed if you're planning on keeping
any text before the start position while making the replacements after it.

With the fifth argument you can say how many replacements are to be made.
'Make only the first 3 replacements
txtCode.Text = Replace(txtCode.Text, "VB 5.0", "VB 6.0", , 3)
Replace can compare strings before attemping to replace them. Set the sixth
argument to 1 (for case-insensitive Text comparison) or 0 (case sensitive Binary
comparison). There are of course constants as well - vbTextCompare,
vbBinaryCompare.
txtCode.Text = Replace(txtCode.Text, "VB 5.0", "VB 6.0", _
txtCode.SelStart + 1, 3, vbTextCompare)
With the new Replace function, you have the tool needed to put this window into
your application.

54
Join and Split functions (VB 6.0 only)

I'd like to define two terms which should be understood before looking at Split and
Join - parse and delimited. Parsing means to break down a string into it's smaller
parts, which are separated (delimited) by a particular character.

Another InStr nightmare has been solved by some handy new functions. Have you
ever had a long comma-delimited string which needed to be parsed? Without the
Split function, you would do something crazy this:

Dim s As String, sItems() As String


Dim iPos As Integer, iCount As Integer

s$ = "dog,cat,pig,boy,girl,man,woman"

Do While Len(s$) > 0


iPos% = InStr(s$, ",")
iCount% = iCount% + 1
ReDim Preserve sItems(iCount% - 1) As String
sItems(iCount% - 1) = Left(s$, IIf(iPos% = 0, Len(s$), iPos% - 1))
s$ = IIf(iPos% = 0, "", Right(s$, Len(s$) - iPos%))
Loop
The above code fills a zero-based string array (sItems) with data from the longer
string (s). Exactly what the new VB 6.0 Split function can do like this:
Dim s As String, sItems() As String
s$ = "dog,cat,pig,boy,girl,man,woman"
sItems() = Split(s$, ",")
So you Split your string up, made some adjustments to the sub-strings, and now you
need to put your string back together for easy storage...no problem, just Join them
back up:
s$ = Join(sItems(), ",")
StrReverse function (VB 6.0 only)

In all my coding days, I've never had the need to reverse a string, but Bill tells me I
need this function. So let's play around and see how it would be done without VB
6.0.

Dim s As String, i As Integer


s$ = "A man, a plan, a canal, Panama"

For i% = Len(s$) To 1 Step -1


s$ = s$ & Mid(s$, i%, 1)
Next i%

s$ = Right(s$, Len(s$) / 2)
If you're a proud owner of VB 6.0, you can write the same code like this:
Dim s As String
s$ = "A man, a plan, a canal, Panama"

55
'Returns "amanaP ,lanac a ,nalp a ,nam A"
s$ = StrReverse(s$)
Space function

This function by itself produces a certain number of spaces. It's best use is to clear
fixed-length strings.

sRecord$ = Space(128)
String function

This function is used for producing a string with a certain number of repeating
characters. It's two arguments are the number of characters and the character code
to repeat.

sResult$ = String(10, 65)

65 is the character code for upper case "A", therefore the result of this function is
"AAAAAAAAAA". Use this function to create a buffer string of null characters for INI
file access.

Character Codes

Strings are made up of individual characters, or bytes. Each of these characters, as


well as the non-characters keys (like Enter, Backspace, and Tab) has a code
somewhere between 0 and 255. When the user types at the keyboard, these codes
are automatically sent to the appropriate control's Keypress event in the form of the
KeyAscii integer argument and to the KeyUp and KeyDown events in the form of the
KeyCode integer argument. By appropriate control, I mean the control which has the
focus.

Difference Between KeyCode and KeyAscii

If you're dealing strictly with characters (letters, numbers, punctuation, and


symbols) then inspecting the KeyAscii argument in the KeyPress event should
suffice. If you need to detect "actions" (such as the arrow keys, Enter, Backspace,
Tab), function keys (F1 - F12), or whether Ctrl, Shift, or Alt is being held down, then
you'll need to look at the KeyCode argument in either the KeyUp or KeyDown event.

See Page 9: Handling Keyboard Input for more information.

StrComp function

This function compares two strings, and tells you if they are equal, or if one is less
than or greater than the other. This probably sounds odd - how can one string be
less than another? It's all judged by the ASCII values of the characters. Upper case
letters have a lower ASCII value (65-90) than lower case letters (97-122). ASCII
values of numeric characters (48-57) are less than all letters. If the first characters
of each string being compared are equal, the second character is then compared.

• "Bob" is less than "bob"


• "123Go" is less than "Go123"
• "John" is greater than "Jim", but less than "jim"

56
You give the function two strings, and an optional third argument for the type of
comparison. If the compare argument is omitted, the default comparison method is
used. (See Option Compare Text). The constants available are vbBinaryCompare and
vbTextCompare.

The function returns -1 if the first string is less than the second, 1 if the first string is
greater than the second, and 0 if they match. Since zero can be considered to be a
False value, you might use the function like this:

If Not StrComp("some string", "Some String", vbTextCompare) Then


'Strings match
End If
StrConv function

This function converts a string to upper, lower, or proper case. Since we already
have the UCase and LCase functions, the best use of StrConv is to convert to proper
case, which means the first letter of every word in the string will be capitalized.

Dim myString As String


myString$ = "my brain hurts"
'Returns "My Brain Hurts"
myString$ = StrConv(myString$, vbProperCase)
StrConv can also make conversions from the 8-bit, 256-character ASCII character
set to the 65,536-character UNICODE standard and vice versa. Unicode is a 16-bit
character set which is large enough to hold all of the characters of all of the world’s
languages.
Asc function

Use this function to get a character's code. Normally, you would use a single
character enclosed in quotes or one character resulting from another function, but
using a multi-character string doesn't cause an error; the function will only look at
the first character of the string.

'iResult% = 80
sCity$ = "Providence"
iResult% = Asc(sCity$)
Chr function

Using this function is like using one literal character. However, you can't type some
characters. You may need to add carriage return-line feeds to your strings. You'd do
this with the combination of Chr$(13) & Chr$(10)

'Display the message on two lines


MsgBox "First line of text" & _
Chr$(13) & Chr$(10) & "Second line of text"

Note: You can use VB's built-in constant vbCrLf instead of these two characters.

VB interprets double quotes in a code window as the beginning of a string. To add


double quotes within a string, use Chr(34).

Dim msg As String


msg$ = "We called her " & Chr(34) & _
"the mother-in-law from hell" & Chr(34)
Concatenation

57
Note that strings can be glued together with an ampersand (&). The ampersand is
called the concatenation character. Concatenation is the combining of strings, string
variables, string properties, and string functions to create one longer string. In older
versions of VB, the plus sign was used for this, and is still a valid concatenation
character, but hardly used anymore. Stick to the ampersand to avoid any confusion
with numeric addition statements.

Page 20: If-Then-Else and Select Case


View Page Comments Leave a Comment

If-Then-Else and Select Case statements are the backbone of VB's decision making
power. Every program you'll write will need to evaluate certain conditions at one
time or another.

One Line If-Then-Else

Use for quick, uncomplicated scenarios.

'If condition Then do this Else do that


'Prevents a new instance of your app from starting,
'Otherwise runs a general procedure named InitProgram
If App.PrevInstance = True Then Unload Me Else InitProgram

Condition is any kind of expression which evaluates to True or False. You'll compare
one value to another with an equals sign.

If the condition is true, VB executes the code after Then and ignores the code after
Else. If the condition is False, the opposite is true. However, Else is not required, just
use If and Then when no code needs to run upon finding the condition False.

If App.PrevInstance = True Then Unload Me


IIf function

IIf stands for Immediate If and is a great function which most programmers
overlook. The function is basically an assignment statement where one of two is
values is set, based on a True/False condition. Below is a very elegant does this file
exist? function.

Public Function FileExists(ByVal sPath As String)

FileExists = IIf(Len(Dir(sPath)), True, False)

End Function
Block Style If-Then-Else

With the block-style If-Then-Else, you can check multiple conditions and run various
code segments depending on the result of those conditions.

If Year(Now) > 2000 Then


MsgBox "Well, we got through it!"
ElseIf Year(Now) = 2000 Then

58
MsgBox "You're still here?"
ElseIf Year(Now) = 1999 Then
MsgBox "Are you prepared for armageddon?"
Else
MsgBox "Your system time is incorrect"
End Sub

Note the second and third conditions are preceded by ElseIf. There can only be one If
line, but ElseIf can be used repeatedly. Again here, Else is not required unless you
want to run code when VB finds all the conditions False. Don't forget to finalize the
block-style If-Then-Else with End If. I can't how many times I've aggravated myself
when I ran my project and got the error message "Block If Without End If".

And, Or, Xor Operators

Use these operators to satisfy mutiple conditions before running a code segment.

With Screen
If .Width / .TwipsPerPixelX = 640 And _
.Height / .TwipsPerPixelY = 480 Then
MsgBox "Your screen resolution is set to 640x480"
End If
End With
The code runs if both conditions are True.
'Surpress the Enter and Backspace keys
If KeyCode = vbKeyBack Or KeyCode = vbKeyReturn Then
KeyCode = 0
End If
The code runs if either conditions are True.
If bSection1Done = True Xor bSection2Done = True Then
MsgBox "You must complete both sections"
End If
The code runs if one (and only one) condition is True.
If Not

If Not is the exact opposite of If, the code segment will run if a condition is False.

Dim Done As Boolean


Done = True
If Not Done Then LetsFinish

The LetsFinish procedure will not run. Notice we just used the boolean variable by
itself. If Not Done is equivalent to If Done = False and If Done is the same as If
Done = True.

Nested If-Then-Else

You can put entire If-Then-Else blocks of code within other blocks. This is called
nesting. Use this method when you need to run code (or not) when satisfying one
condition, and run some different code only upon satisfying other conditions.

If (first condition) Then


'code segment 1
If (second condition) Then
'code segment 2
Else
'code segment 3
End If
Else

59
'code segment 4
End If

Code segment 1 runs if first condition is True. Code segment 2 runs if first condition
and second condition are True. Code segment 3 runs if first condition is True and
second condition is False. Code segment 4 runs if first condition is False (regardless
of second condition).

Like operator

When comparing strings, you can use the Like operator to see if the string matches a
pattern.

Dim sName As String


sName$ = InputBox("Enter your name")
Find out if user's name begins with a J
If sName$ Like "J*" Then
(code segment)
End If
If TypeOf...Is

This variation of the If statement is used to find out if an object is of a certain type.
Most often, you'll use this when you have the name of a control and want to find out
what kind of control it is. Click here to see If TypeOf...Is used with cut, copy, paste.

Select Case

Suppose you set the value of a variable to a random number between 1 and 100.
You would need numerous If and ElseIf lines to completely evaluate the value of the
variable. Hello, Select Case.

The Case part of Select Case is the condition that VB evaluates. You can then use
one of the six methods below to pinpoint the value of the Case and run the
appropriate code.

iRandom% = Int(Rnd * 100) + 1


Select Case iRandom%
Case Is < 10 '(Uses the Is keyword with the less than operator)
'do this
Case 10 '(Goes directly for one particular value)
'do this
Case 11 To 45 '(Uses the To keyword for a range of values)
'do this
Case 46, 49, 68, 87 '(Separates unrelated values with commas)
'do this
Case Is < 99 '(Uses the Is keyword with the greater than operator)
'do this
Case Else '(Goes after all values not mentioned otherwise)
'do this
End Select

Don't use Case Is = (some value) and be sure to include the End Select.

Select Case can also evaluate strings.

Use the To keyword to see if a string is within an alphabetical range.

60
Dim iRoomNumber As Integer
Dim sLastName As String
sLastName$ = "Johnson"

'iRoomNumber% = 109
Select Case sLastName$
Case "A" To "E": iRoomNumber% = 204
Case "F" To "L": iRoomNumber% = 109
Case "M" To "R": iRoomNumber% = 300
Case "S" To "Z": iRoomNumber% = 112
End Select

Page 21: For...Next and Do...Loop


View Page Comments Leave a Comment

For...Next and Do...Loop are block statements used for running code segments over
and over again. For...Next is a counter loop, meaning you set the loop to run a
certain number of times. Do...Loop includes a True/False condition which governs
how many times (if any) the code segment runs.

For...Next
Dim i As Integer
For i% = 1 To 10
MsgBox "Loop #" & i%
Next i%

This is the simplest of For...Next loops. The code segment between For and Next will
repeat itself ten times. Many of your For...Next loops will use this basic method.
You'll supply a counter variable and the range to be looped. It's very common to use
the simple i name for the counter variable.

A For...Next loop does not have to start counting at 1, or count by 1 either. The
following code can be copied to any Form's Paint event to draw a 32x32 grid as the
background.

Private Sub Form_Paint()

Dim x As Single, y As Single

For x! = 0 To ScaleWidth Step ScaleWidth / 32


Line (x!, 0)-(x!, ScaleHeight)
Next x!

For y! = 0 To ScaleHeight Step ScaleHeight / 32


Line (0, y!)-(ScaleWidth, y!)
Next y!

End Sub

This loop counts by the Form's ScaleWidth divided by 32, whatever that value
happens to be. This was accomplished by adding the Step keyword. If Step is
omitted, the loop defaults to counting by 1.

You can create some nifty loops by using the counter variable right in the code
segment.

Exit For statement

61
You can terminate the looping process with an Exit For statement. Usually, Exit For is
used after checking some condition with an If-Then-Else or Select Case statement.

Below is a quick routine which gets the user's CD-ROM drive letter.

Dim rv As Long, i As Integer


Dim sCDROMLetter As String
Const DRIVE_CDROM = 5

For i% = 65 To 90
rv& = GetDriveType(Chr(i%) & ":\")
If rv& = DRIVE_CDROM Then
sCDROMLetter$ = Chr(i%)
Exit For
End If
Next i

The range of the loop is set up from the ASCII value of 'A' to the ASCII value of 'Z'.
When the CD drive is found, the loop terminates.

Note: GetDriveType() is an API function. This function and also the DRIVE_CDROM
constant can be added to your project by copying them from your API Text Viewer.

Do...Loop

One of the following must be associated with a Do...Loop.

• While or Until keyword


• Exit Do statement
• DoEvents statement

Dim Done As Boolean


Do Until Done
'(code segment)
Loop

This loop will run continuously until the Done variable becomes True somewhere in
the code segment.

'Another way to write it...


Do While Not Done
'(code segment)
Loop

'...and another...
Do
'(code segment)
Loop Until Done

'...and yet another


Do
'(code segment)
Loop While Not Done

Using While or Until in the Loop line guarantees the code segment will run at least
once.

Here's some example code which loads win.ini into a ListBox

62
Dim h As Integer, sTemp As String
h = FreeFile
Open "c:\windows\win.ini" For Input As #h
Do While Not EOF(h)
Line Input #h, sTemp$
lstWinIni.AddItem sTemp$
Loop
Close #h

Note: The path used with the Open statement is hard-coded and would not work on
all computers. Not everyone uses 'c:\windows' as their Windows directory

While...Wend

While...Wend is a little outdated, but you may find it useful. It works identically to
using the Do While...Loop syntax.

While Not Done


'(code segment)
Wend
Exit Do statement

Terminate the loop with an Exit Do statement.

Do
If Year(Now) > 1999 Then Exit Do
'(code segment)
Loop

This loop will immediately terminate if the new millenium has begun.

DoEvents statement

DoEvents can be put anywhere in your code, but is mostly used within loops. Using
this statement tells VB to check and see if any events are pending and need to be
processed. Loops take total control of the processor, so adding a DoEvents statement
lets the processor respond to say, a user click or keypress and then return back to
the loop. As long as DoEvents statements are present, an entire program can
actually run within a Do...Loop. These kinds of loops are called idle loops, meaning
the code contained in the loop will only run while VB is idle (not responding to
events).

Do
'(code segment)
DoEvents
Loop

DoEvents requires no arguments and can also be used as a function which returns
the number of visible forms in your program.

Dim iNumForms As Integer


iNumForms% = DoEvents

Page 22: Variable and Control Arrays

63
View Page Comments Leave a Comment

Generally speaking, an array is a group of related things. As your projects become


more complex, you may need to use a large amount of variables and controls. By
creating variable arrays and control arrays, you can tremendously simplify your
code.

Variable Arrays

Let's say you want to create an array of 100 related variables. A slight variation of
the Dim statement will accomplish this for you.

Dim iNumber(1 To 100) As Integer


Dim iNumber(0 To 99) As Integer
Dim iNumber(99) As Integer

Any of these statements is valid. You might be more comfortable having the first
element of the array numbered 1. In that case, use the first example. The second
example uses the method I prefer, Base 0. The third example is identical to the
second assuming you are using Base 0.

Note: Base 0 is not only the method I prefer, but the future of Visual Basic. The next version of VB (.NET), will
only allow arrays of Base 0

Base 0 and Base 1

VB is, by default, set to Base 0. You can override that by using the first example
from above. (This will only override it for that variable array.) You can set an entire
form or code module to Base 1 by putting the Option Base 1 statement in the
general declarations section.

So, if you're form or module is set to Base 1, the third example from above will
create an array of 99 variables. Avoid this confusion by using the To keyword and
specifically numbering the lower bound and upper bound of your variable array.

Dim iNumber(15 To 114) As Integer

Here's another example of creating a 100-element variable array. Having the lower
bound equal to 0 or 1 is not required. This method would be useful in a loop where
you'd want the subscripts to match some other portion of your code. (Most likely a
For...Next loop)

Subscripts
Dim sMessage(0 To 14) As String

The 15 elements of this variable array are numbered from 0 To 14. The number of
an element is its subscript. Use one particular element of the array by enclosing it's
subscript in parentheses. Do not use a type declaration character with an array.

sMessage(12) = "You must enter some data first"


MsgBox sMessage(12), vbInformation, "Alert"
Dynamic Variable Arrays

64
Dynamic means changeable. You can add or remove elements from a variable array
during program operation. This is done with the ReDim statement. ReDim is only
used at procedure level and does not create the actual array, so you must Dim the
array beforehand with empty parentheses.

Dim sMessage() As String


ReDim sMessage(0 To 20) As String

This ReDim statement creates 21 elements at run time. You can use ReDim any
number of times, however, ReDim, like Dim, will initalize all the elements of this
array to an empty string(""). To avoid erasing any data previously assigned to
elements of the array, use the Preserve keyword.

ReDim Preserve sMessage(0 To 20) As String


Erase statement

If re-initializing the data in an array is your intent, then use the Erase statement and
the name of the array without any parentheses.

Erase sMessage

In a regular array, the Erase statement will simply initialize all the elements. (False
for Boolean, 0 for numbers, and "" for strings). In a dynamic array, Erase will also
release all the memory allocated to the array.

LBound and UBound functions

If you try to use an element of an array which is too large or too small, you'll get a
"Subscript out of range" error. Use the LBound and UBound functions to help avoid
that.

Dim sMessage(0 To 6)
sMessage(6) = "Today is Saturday"
iResult% = UBound(sMessage) 'iResult = 6
iResult% = LBound(sMessage) 'iResult = 0
sResult$ = sMessage(UBound(sMessage))'iResult = "Today is Saturday"
Array Function

Creating a small array and assigning values to each of its elements can be done
quickly with the Array function.

Using this method forces the array to be of the Variant type and can contain
numbers or strings, but is accessed the same way.

Dim vData As Variant


Dim i As Integer
vData = Array("Bob", 27, "Matt", 24, "Allison", 21, "Danielle", 16)
For i% = 0 To 6 Step 2
lblInfo(i%).Caption = vData(i%)
Next i%

In this example, we've set up an array with alternating names and ages as its
elements. The For...Next loop assigns every other element to every other Caption
property of a Label control array. As you can see, For...Next loops and arrays go
hand-in-hand.

65
Control Arrays

Like variable arrays, control arrays have the same name with a unique subscript. You
create a control array at design time by naming one control and giving another
control of the same type the same name. When you attempt this, VB will prompt you
with "You already have a control named (name), do you want to create a control
array?" Obviously, you click yes and it's done. To add more controls to the control
array, simply give it the same name and the next available subscript will be
automatically added.

With control arrays, we normally don't say subscript, but rather the subscript is
stored in the Index property of the control. Once the control array has been created,
you can change the Index of a control at will, but it's a good idea to keep them
consecutive for use in For...Next loops.

Dim i As Integer
For i% = 0 To 49
txtInput(i%).Text = ""
Next i%

This code segment clears the text from 50 Text Boxes. Try doing that without a
control array.

Creating controls at run time

Drawing large control arrays at design time may be a waste of time and resources. A
more efficient way to use control arrays is to create them at run time with the Load
statement.

First, you must draw a control from which VB can make a copy and set it's Index
property to a numeric value (most likely 0). Set the appropriate properties of this
template control at design time. Newly created controls will have all the same
property settings as the template control. After creation, you'll probably need to set
some properties individually, especially Left and Top.

Take a look at this code which creates 20 Text Boxes and sets them up in 5 columns
and 4 rows. Assume the ScaleMode of the Form is twips.

'One control named txtInput(0) already exists


'Index property is set to 0, and Visible is set to False
Dim i As Integer
For i% = 1 To 20
Load txtInput(i%)
With txtInput(i%)
.Width = 360: .Height = 240
.Left = ((i% - 1) Mod 5) * .Width
.Top = ((i% - 1) \ 5) * .Height
.Visible = True
End With
Next i%

Newly Loaded controls are not initially visible, so be sure to set the Visible property
to True.

Creating controls with the Controls collection (VB 6.0 only)

66
Visual Basic 6.0 has the capability to create controls from scratch by using the Add
method of the Controls collection (which is actually a property of your Form). You
will not be able to create control arrays this way because the Index property will not
be available. Also, in order to have the newly created control respond to events,
you'll need to specially set up a Private object variable in the Form's general
declarations:

Private WithEvents picNew As PictureBox


Notice the WithEvents keyword has been added here. This tells VB that you will be
creating a new object and wish to have event routines to write code for. Once this is
done, you'll be pleasantly surprised to find that picNew has appeared in the object
list of code windows, and all the event subs are there waiting for code, even though
the control doesn't actually exist yet. To actually create the control at run time, you'll
need Set statement, a keyword which is used exclusively for object creation.
Set picNew = Controls.Add("VB.PictureBox", "Picture1", Me)
The first argument is the string class name of the control. If you don't know the
exact class name, go to your Toolbox, pause your mouse over the icon of the control
and put "VB." in front of it. The second argument is what you want to name the
control (it's Name property), and the third argument is an optional way to specify
what container this new control will be assigned to...usually you'll leave this out and
let it default to the current Form, otherwise use the name of a Frame or PictureBox
(without the quotes). Now you can begin setting properties, most likely starting with
Visible. You can refer to the new control one of two ways, by it's object variable, or
as a member of the Controls collection.
With picNew 'or... With Controls("Picture1")
.Visible = True
.Left = 120
.Top = 240
.AutoSize = True
.Picture = LoadPicture(App.Path & "images\mypic.jpg")
End With
Dynamically created controls can also be "unloaded" with the Remove method. Either
example below will unload our PictureBox when it is double clicked.
Private Sub picNew_DblClick()

Controls.Remove "Picture1"
'or...
Controls.Remove picNew

'This can't hurt


Set picNew = Nothing

End Sub

Page 23: Write Your Own Procedures


View Page Comments Leave a Comment

Code which runs in response to an click or other event is an event procedure, or


sometimes called a routine. If you have code segments which you want to run in
more than one event, it's a good idea to store them in general procedures. You can
then run these procedures when needed with one line of code.

General procedures will...

1. ...save keystroke time.

67
2. ...reduce the size of your program.
3. ...make debugging easier.

Most programmers create their own library of general


procedures which can be used in any of their applications.

The only difference between event procedures and general


procedures is the way they are called. Code in event
procedures are called by internal Windows messages; code in general procedures are
called specifically by you, the programmer.

Private and Public Subs

You may have noticed that event procedures always start with Private Sub. Private
means that code in other modules cannot call the procedure. On the other hand, a
Public Sub can be called from anywhere in the project. When creating procedures,
Public is the default and the actual Public keyword is not required.

Tip: If needed, you can convert your event procedures to Public and call them from
other Forms or Modules.

Public Sub cmdOK_Click()


'Code segment
End Sub
Call the event procedure:
cmdOK_Click
Add Procedure Window

Open the module where the procedure will be located, then select Tools/Add
Procedure to bring up the Add Procedure window, where you can name your new
procedure, decide between Public and Private, Sub or Function, and whether to use
all Static variables. Two more choices (Property and Event procedures) are advanced
procedures used with Class Modules.

If you have a procedure which would only be associated with one Form, then writing
it in the form (.FRM file) would be OK, but most of the time you'll write it in a .BAS
file (code module) which is part of your project. You never know when you might
want to reuse the procedure.

Follow the variable naming rules when naming procedures.

Once you click OK, you are whisked to the code window for your new procedure
where End Sub (or Function) and some empty parentheses have been added
automatically.

So what are the empty parentheses for? You can optionally send data to the
procedure in the form of arguments. Arguments are the additional information that a
procedure needs to complete itself. Like variables, arguments are of a certain data
type and must be written into the procedure as such.

Public Sub FindReplace(ByRef sSearchString$, _


ByVal sFindWhat$, ByVal sReplaceWith$)

68
Dim iPos As Integer, iStart As Integer
iStart% = 1
Do
'Find beginning position
iPos% = InStr(iStart%, sSearchString$, sFindWhat$)
'If not there, then get out
If iPos% = 0 Then Exit Do
'Combine left portion, new string, and right portion
sSearchString$ = Left(sSearchString, iPos% - 1) & sReplaceWith$ & _
Right(sSearchString$, Len(sSearchString$) - iPos% - Len(sFindWhat$) + 1)
'Calculate where to begin next search
iStart% = iPos% + Len(sReplaceWith$)
Loop

End Sub

This Find and Replace procedure requires three strings to be sent to it in order to run
properly. This is called passing arguments. You then use the arguments in the code
just like variables.

ByVal and ByRef

Arguments are passed "by value", or "by reference". ByRef is the default and can be
omitted.

It's popular opinion that ByVal should be the default because most of the time you
just want to use the value of the variable in the procedure, rather than the actual
variable. If you specifically want the procedure to change the value of an argument
(as the above procedure did), then use ByRef, otherwise stick the ByVal keyword in
front of your arguments.

Call the procedure by simply writing its name and its arguments in the correct order.
Separate the arguments with commas. Parentheses are not required with a Sub
procedure.

FindReplace myOriginalString$, myStringToReplace$, myNewString$

Be sure to send the right data types for the arguments. Once the procedure has run,
program execution is returned to the very next line in the procedure that called it.

Call statement

The Call statement is optional and is used less and less all the time. If you want a
visual clue to indicate that this line of code is calling a separate procedure, then by
all means include it. If you do use Call, you must enclose the arguments in
parentheses.

Call FindReplace(myOriginalString$, myStringToReplace$, myNewString$)

By now, you know that Visual Basic has many built-in methods such as the AddItem
method of a ListBox. A VB method simply performs an action by running a built-in
code segment. Sub procedures are methods which you define, thereby expanding
VB's capability. Hold on, don't methods have to be associated with an object? Yes,
the Sub procedures you write are methods of the object you place them in, whether
a Form or Module.

69
Assuming you've written the above Sub procedure in a Form named frmMain, you
could actually call the procedure like this:

frmMain.FindReplace myOriginalString$, myStringToReplace$, myNewString$

On the other hand, VB has many built-in numeric, string and boolean functions. You
can further expand this collection of functions by writing your own Function
procedures, which are also considered to be Methods.

Function Procedures

You know that a function produces a result and when using one of VB's functions you
put the result in a variable or property of an object. The same is true of Function
procedures.

Create your Function procedure in the Add Procedure Window, write in any
arguments and then declare what data type the result of the function will be.

Public Function GetERA(ByVal iIP As Single, ByVal iER As Integer) As Single

GetERA = Format(iER% * 9 / iIP!, "##.00")

End Function

This is a simple function which takes two arguments to produce a baseball pitcher's
Earned Run Average and returns the result to the procedure that called it.
Somewhere in the code (usually the last line) you have to assign a value to your
function. This function could be called like this:

Dim sinERA As Single


sinERA! = GetERA(123.333, 54)

You may decide to use a Function even if the only value you need returned is
whether the function was successful or not.

Public Function FolderToFloppy(sFolder As String) As Boolean

On Error GoTo FunctionFailed


Dim sFile As String
If Right(sFolder$, 1) <> "\" Then sFolder$ = sFolder$ & "\"
sFile$ = Dir(sFolder$ & "*.*")

Do While sFile$ <> ""


FileCopy sFolder & sFile$, "A:\" & sFile$
sFile = Dir
Loop

FolderToFloppy = True
Exit Function
FunctionFailed:

End Function
'Note: some computers may not use A:
as the floppy drive letter. Look into
the GetDriveType API function.

70
This function takes a folder as an argument and copies all it's files to the floppy
drive. The argument is passed by reference because we check for a trailing
backslash. If an error occurs, the function terminates.

Calling this function might be done like this:

If Not FolderToFloppy(App.Path) Then


MsgBox "Error copying files to floppy"
End If

Note: Creating a new general procedure can also be done by going to the general
declarations section of the form or code module and typing Sub (procedure name)
and pressing enter. End Sub and some empty parentheses are added automatically.
This new procedure doesn't actually exist in general declarations, it can just be
initially written there.

Passing arrays to procedures

Note: VB 6.0 can now return an array from a function. Personally, I haven't found a
use for this new trick yet

A Function procedure returns just one value, it cannot return an array of values. The
work-around to this is to pass an entire array to a procedure as an argument. An
array is passed with empty parentheses and always ByRef.

'A simple procedure which doubles


'the arguments passed to it
Public Sub DoubleThem(myArray() As Integer)

Dim i As Integer

For i% = LBound(myArray) To UBound(myArray)


myArray(i%) = myArray(i%) * 2
Next i%

End Sub

'Passing an array to this procedure


Dim myArray(1 To 3) As Integer
myArray(1) = 34
myArray(2) = 57
myArray(3) = 123
DoubleThem myArray()
The values in the variable array change to 68, 114, and 246.
Passing objects to procedures

Forms, controls, and other objects can also be passed to procedures as arguments.
The type of the object is written into the procedure rather than a standard data type.
The following Sub procedure adds an item to a ListBox only if the item doesn't
already exist.

Public Sub AddItemNoDuplicates(ByVal lst As ListBox, ByVal sItem As String)

Dim i As Integer

For i% = 1 To lst.ListCount - 1
If sItem$ = lst.List(i%) Then
MsgBox "This item already exists in the list", _
vbInformation, "Duplicate found"

71
Exit Sub
End If
Next i%

lst.AddItem sItem$

End Sub

'Using the procedure


AddItemNoDuplicates lstItems, "New Item"
You could make this procedure work with both ListBoxes and ComboBoxes by
specifying the generic type Control instead of a specific control type.
Public Sub AddItemNoDuplicates(ByVal ctl As Control, ByVal sItem As String)
Parent and Container properties

When passing a control as an argument, you can access that control's Form or
Container with these properties. The procedure below frames a control when it gets
the focus.

Public Sub FrameOnFocus(ctl As Control)

Dim l As Integer, t As Integer, w As Integer, h As Integer


l% = ctl.Left - 4
t% = ctl.Top - 4
w% = ctl.Left + ctl.Width + 8
h% = ctl.Top + ctl.Height + 8
With ctl.Parent
.DrawWidth = 4
.Cls
End With
'Can't use Line method in a With block
ctl.Parent.Line (l%, t%)-(w%, h%), 0&, B

End Sub

'Call FrameOnFocus in each control's GotFocus event


Private Sub txtCity_GotFocus()

FrameOnFocus txtCity

End Sub
The Container property would be used to access the control's PictureBox or Frame
container. If the control is not contained by a container control, this property returns
the Form.

Here's a goofy little procedure which takes a Form as an argument and shrinks it just
before unloading.

Public Sub DissolveForm(ByVal frm As Form)

With frm

Do Until .ScaleHeight < 2


.Height = .Height - 100
.Width = .Width - 100
.Left = .Left + 50
.Top = .Top + 50
DoEvents
Loop

End With

End Sub

'Best called from QueryUnload

72
DissolveForm Me
The Me keyword is a built-in global variable which is used to pass the current
instance of a Form to a procedure. Me is intended to be used when you have multiple
Form instances, but you will see programmers using it as shortcut to writing a
Form's name. An example of creating multiple Form instances can be seen on Page
33: MDI Forms

Page 24: Input/Output for Text Files


View Page Comments Leave a Comment

How Should I Store My Data?

Opening a Text File

You can't read a book without opening it first. The same is true of files, but you also
must tell VB what you plan to do with the file and in order to refer to the file after
opening it, you need to give it a number.

Open App.Path & "\myFile.txt" For Input As #1

The Open statement is followed by the full path to the text file (see App object). For
Input tells VB that you will be reading from this file. As #1 gives this file the
number 1 to be used with other Input/Output statements.

To write to a file, you would use For Output or For Append. If you open a file For
Output, be prepared to re-write the entire file. Opening a file For Append allows you
to write data to the end of the file, thereby saving what was already there. Opening
a file For Binary is another alternative, but won't be discussed in the beginner
tutorial.

Using the comma-delimited format

After opening a file For Output or For Append you can write to it with the Write #
statement.

lblTeam.Caption = "Red Sox"


iWins% = 78: iLosses% = 84
Open App.Path & "\myFile.txt" For Output As #1
Write #1, lblTeam.Caption, iWins%, iLosses%
Close #1

Since the file we want to write to was assigned #1 in the Open statement we begin
the line with Write #1 and follow it with values separated by commas. The values
can be actual numbers, strings, numeric or string variables, or numeric or string
properties. Write # automatically puts quotes around strings, separates the values in
the file with commas and attaches newline characters (Chr$(13) & Chr$(10)) at the
end. Use additional Write # statements to put data on the next line in the file. If
you're done with this file, close it with the Close # statement. You must close it if
you plan to use it in a different mode such as For Input.

This output will appear in the file like this:

73
"Red Sox",78,84
To input data written to a file with the Write # statement, use the Input #
statement.
Dim sTeam As String, iWins As Integer, iLosses As Integer
Open App.Path & "\myFile.txt" For Input As #1
Input #1, sTeam$, iWins%, iLosses%
Close #1

Unlike Write #, Input can only read into variables, don't try to use properties. You
can use regular variables or elements of a variable array. Make sure the variables
are of the right data type. Obviously, you don't just go blindly reading a file with this
method, you should know how the file is laid out.

Not-delimited format
Open App.Path & "\myFile.txt" For Append As #1
Print #1, "Jim"; "Johnson"; "213-09-0031"
Close #1

In this example, we've got a file opened For Append and we're adding three values
to the end of the file. Print # will not separate the values with commas or enclose
strings in quotes. Any additional Print # statements will write to the next line in the
file. Notice the values in the Print # statement are separated with semi-colons. The
output to the file in this case will appear like this:

JimJohnson213-09-0031

Printing to a file like this would probably be better for entire paragraphs rather
smaller pieces of data.

Open App.Path & "\myFile.txt" For Output As #1


Print #1, "First", "Last", "SSN"; Spc(11)
Print #1, "Jim", "Johnson", "213-09-0031"; Spc(3)
Print #1, "Bill", "James", "321-00-9875"; Spc(3)
Close #1

Here the values are separated with commas instead of semi-colons. The purpose of
this is to spread out the data into 14-character wide columns. The output from this
example will look like this:

First Last SSN


Jim Johnson 213-09-0031
Bill James 321-00-9875

With this method you could set up a file in a table-like structure of rows and
columns. The third column was purposely printed as 14 characters. The Spc function
produced the blank characters. This allows you to know the number of characters per
line, which would be 44 in this case (3 columns @ 14 characters + 2 characters for
carriage-return/line-feed)

To read not-delimited data, you'll need the Input function. This function starts
reading from the current position in the file. When a file is first opened, this position
is 1. Prior to using the Input function, the file position can be adjusted with the Seek
statement. You then give the number of characters to read to the Input function

74
(along with the file number). Take a look at the general procedure below. We pass it
a row number and a column number to produce a desired piece of data.

Public Function GetStringByRowColumn _


(ByVal iRow As Integer, ByVal iColumn As Integer) As String

On Error GoTo errorhandler

Const ROW_SIZE = 44
Const COLUMN_SIZE = 14
Dim sTemp As String

Open App.Path & "\myfile.txt" For Input As #1


'Seek to correct file position
Seek #1, ((iRow% - 1) * ROW_SIZE) + _
((iColumn% - 1) * COLUMN_SIZE)
'Input 14 characters into temporary variable
sTemp$ = Input(14, #1)
'Get rid of excess spaces
GetStringByRowColumn$ = Trim$(sTemp$)
Close #1

Exit Function
errorhandler:
MsgBox Err.Description

End Function
The function might be called like this:
txtLast.Text = GetStringByRowColumn(3, 2)

Sometimes you may want to input an entire file. Knowing how many characters the
file contains is not necessary. For this, we have the LOF function. LOF stands for
Length of File and is used like this:

Open App.Path & "\myFile.txt" For Input As #1


txtMain.Text = Input(LOF(1), #1)
Close #1

The LOF function takes one argument, the number of the open file. Don't use the #
symbol with the LOF function. Make sure your Text Box (or wherever you're putting
the input) is large enough to display the whole file. Most often you'd use a multi-line
scrollable Text Box. Also, make sure that you are currently at the first position in the
file, otherwise you'll go past the end of the file and get an error.

Note: The # symbol is not required in any input/output statement, but most
programmers are in the habit of using it.

LOF can only be used on an open file. If you need the size of a closed file you can
use the FileLen function.

Dim lSize As Long


lSize& = FileLen(App.Path & "\myFile.txt")
Line Input # statement

With the Line Input # statement you can input one line at a time into a variable. You
can then inspect that line, do what needs to be done with it, and then go on to the
next line. This is a great way to deal with an entire file line by line.

Dim sTemp As String


Open App.Path & "\myFile.txt" For Input As #1

75
Do While Not EOF(1)
Line Input #1, sTemp$
If Left$(sTemp$, 1) = "[" Then Exit Do
Loop
Close #1

This example uses the EOF function to loop through all the lines in a file. EOF is a
boolean function which returns True when you go past the end of a file. What we're
trying to do here is find the first line in the file which begins with a bracket. The
Do..Loop will input lines as long as EOF is False. Upon finding a line whose leftmost
character is a bracket, the loop terminates and sTemp$ holds the entire line of text
we're looking for.

FreeFile function

You may need to have several text files open at the same time. You could open
additional files As #2 or As #3, etc., but the FreeFile function is a better idea.

(Actually, the only idea. In real world VB programming, As #1, As #2, etc. is never
used. The previous code examples did not mention FreeFile purposely, in order to
introduce the reader to file numbers.)

Dim hFile As Integer


hFile% = FreeFile
Open App.Path & "\myFile.txt" For Input As hFile%

The FreeFile function requires no arguments. Assign the return value of the FreeFile
function to a variable and then use that variable in other input/output statements.
This variable begins with an 'h' prefix, because a file number is actually a Windows
Handle to the file.

Page 25: Database Basics


View Page Comments Leave a Comment

A database file is a collection of database tables. A table is nothing more than a


chart of rows and columns, or a grid...like a spreadsheet. Columns are referred to as
fields and always have headings, or field names. An entire row is one record. Each
individual table in a database contains related information, but it's not uncommon for
a database to consist of just one table. Designing a new database takes careful
planning. For optimal performance in multiple table databases, related fields from
different tables are linked by having the same field name.

See Page 25A: Database Design

VB 5.0 and the Data Control

Database files exist in several formats and versions. With VB 5.0, you'll probably be
using a Microsoft Jet 3.x database. Microsoft Access 97 is the best "front end"
application to use for creating, building, and managing Jet 3.x files.

76
VB 6.0, ADO Data Control, Access 2000, and Jet 4

VB itself comes with a decent front-end database tool called VisData which was
written with VB version 5.0. If you have the 5.0 CD, you also have the whole project
with source code in the samples directory.

Accessing the information in a database with VB is quite simple and actually can be
done without writing code. Placing a Data Control on a form provides a link between
your program and the database file. By itself, the Data Control won't display any
data, you must have bound controls to do that. To find out if a particular control
can be data bound, check to see if it has DataSource property.

Here's how its done. Draw a data control on a form. Set the DatabaseName property
to an existing .MDB database by bringing up the "DatabaseName" dialog box from
the properties window. Now go to the RecordSource property of the Data Control and
you'll see a list of all the tables in the database. Only one table can be assigned.
You'll need additional Data Controls to display more than one table at a time.

Draw a Text Box on the form. First set the DataSource property to the name of the
Data Control (Data1 if you're using the default). Now set the DataField property, this
time choosing from a list of all the fields in the table. Run the project and the Text
Box will contain the data from the first record in that field (making it the current
record). To display an entire record, you'll need a bound control for each field, or
you might want to try one of the database custom controls (DBGrid, DBList,
DBCombo).

Be aware that making changes to bound controls changes the actual data in the
database. Set a Text Box's Locked property to True to avoid that, or use a Label.

Moving around the database

The Data Control has four buttons for moving through its records as you can see
above. You can also move through the records with code.

Data1.Recordset.MoveFirst
Data1.Recordset.MovePrevious
Data1.Recordset.MoveNext
Data1.Recordset.MoveLast

77
The Data control will create a Recordset object to represent the table and allow you
to use it as a property. The Recordset object has many useful properties and
methods of it's own. These four methods require no arguments and will accomplish
the same thing as the user clicking the arrow buttons.

A Recordset is just what it implies, a set of records. In the simplest case, the
Recordset is the entire table, but you can search databases with a query to work
with particular tables, fields, and records. This is done by assigning the
RecordSource property of the Data Control to an SQL string instead of a table name.

Recordsets can be of three different types:

• Table type (dynamic)


• Dynaset type (dynamic)
• Snapshot type (static)

The Table type is one entire table which can be updated (records can be added,
deleted, and edited). The Dynaset type allows fields and records from multiple tables
and is also updatable.

The Snapshot type also allows data from multiple tables, but is not updatable: use
this type when you just need to examine data, rather than update it.

The RecordsetType property of the Data control can be set to create the desired
type. If you do this in code, the following constants are available:

Data1.RecordsetType = vbRSTypeTable
Data1.RecordsetType = vbRSTypeDynaset
Data1.RecordsetType = vbRSTypeSnapShot
RecordCount property

The number of records in your Recordset can be accessed with the RecordCount
property of the Recordset object. However, if the Recordset is of Dynaset or
Snapshot type, then it will be necessary to populate the Recordset first. Populating a
Recordset is accomplished by using the MoveLast method.

Data1.Recordset.MoveLast
MsgBox "There are " & Data1.Recordset.RecordCount _
& " records in this Recordset"

It's not necessary to populate a Table type Recordset, nor is it necessary to populate
any Recordset unless you need the RecordCount right away. Population of large
Recordsets is time consuming. Using the Recordset in other ways will accomplish the
population.

Field data types

Like variables, properties, and functions, Fields in a database must be of a certain


data type. All the data types you're familiar with are available. Microsoft Access uses
these data types.

• Text- String data up to 255 characters

78
• Memo- String data up to 1.2 gigabyte
• Number- Can be further specified as Integer, Long, Single, Double
• Date/Time- Just like the Date data type
• Currency- Just like the Currency data type
• Yes/No- Boolean
• OLE Object- Used for pictures or other objects.

VB 6.0, ADO Data Control, and Access 2000

The Access 97/Jet 3.x database can still be used with VB 6.0. Access 2000 can
convert the file to the new Jet 4 format, but that will not be a good idea if you want
to continue using the intrinsic Data control which revolves around DAO. Jet 4 is more
comfortable with ADO, the new, easy-to-use data object library.

If you're starting a new project and database with VB 6.0, you should go all out and
use the Microsoft Jet 4 database, Access 2000, and the new ADO Data Control.
VisData cannot read the new format.

With ADO, the connect string is now your best friend. Here is a connect string from
one of my projects:

"Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\WINDOWS\Desktop\ASPProj\board\db\board.mdb;" & _
"Persist Security Info=False"
I know, I know...I did say easier to use. No one is expecting you to write these
connect strings from scratch. With VB 5.0 and the Data control, you would use the
DatabaseName dialog box to select the database file. With VB 6.0 and the ADO Data
control, you'll set the ConnectionString property with a 4-step connect string
"wizard".

Once that is done, you'll set the CommandType property to 2 - adCmdTable. This is
similar to setting the RecordsetType property of the intrinsic Data control to 0 -
Table. Now you can set the Recordsource property in much the same manner as
before, but now you'll get a dialog box instead of a drop down list.

Page 26: Printing


View Page Comments Leave a Comment
Printer object

The Printer object represents the user's default printer. You can print text and draw
graphics on the Printer object just as you would do with a Form or PictureBox.
Specifically, you'll use the Print, Line, and Circle methods.

Printer.Print "Testing 1-2-3"


Printer.EndDoc

You must use the EndDoc method of the Printer object to actually begin printing.
Printer.Print will only send output to the printer. Using Printer.EndDoc is like saying

79
"This is everything I want to print, print now!". Use additional Print methods for each
line you want to print.

Formatting one line of text

You can print multiple items on one line. By items I mean regular numbers, strings,
variables, properties, or expressions. You must separate the items with either a
comma or a semi-colon, or you can use the Spc and Tab functions.

Dim sItem1 As String, sItem2 As String


sItem1$ = "Visual": sItem2$ = "Basic"

'Print in 14-character wide columns


'with a comma separator
'Output will be "Visual Basic"
Printer.Print sItem1$, sItem2$

'Print items back to back


'with a semi-colon separator
'Output will be "VisualBasic"
Printer.Print sItem1$; sItem2$

'Embed spaces between items


'with Spc function
'Output will be "Visual Basic"
Printer.Print sItem1$; Spc(1); sItem2$

'Begin printing next item at a certain


'position with Tab function
'Output will be "Visual Basic"
Printer.Print sItem1$; Tab(10); sItem2$
Printer.EndDoc

Always use the semi-colon separator with the Spc and Tab functions to avoid
confusion.

Even though I can't find this mentioned in the help file anywhere, I've found that
numeric output will print with leading and trailing spaces automatically.

Dim iNumber As Integer, sFruit As String


iNumber% = 25: sFruit$ = "apples"

'Output will be " 25 apples 25 "


Printer.Print iNumber%; sFruit$; iNumber%
Printer.EndDoc

You might want to convert all your output to the String data type before sending it to
the Printer.

CurrentX and CurrentY properties

If you've tried any of these samples, you've seen that printing begins in the upper-
left of the page. Send your output to any position of the object you're printing on by
setting the CurrentX and CurrentY properties.

Printer.CurrentX = Printer.Width / 10
Printer.CurrentY = Printer.Height / 10
Printer.Print "Top and left margins set to 10%"
Printer.CurrentX = Printer.Height / 10
Printer.Print "Next line of text"

80
Printer.EndDoc

You must continually set the CurrentX property for the right margin. The CurrentY
property is automatically adjusted for the next printed line.

My text runs off the end of the page!

Let's assume you need to print a long string of text from a multi-line Text Box. For
screen display purposes, the word wrapping is automatically done for you by the
control, but if you try to print it, the text will just run off the edge of the page. This is
where you'll need to do some manual word wrapping.

You might think to break up the string into a certain numbers of characters, but you
must think about different fonts and font sizes also. Another flaw to printing a certain
number of characters per line is that the characters themselves will be different sizes
if you are not printing with a fixed-width font such as Courier. With a propertional-
width font, a string of 100 upper-case "W's" is much longer than a string of 100
lower case "L's".

Get the printed width of a string with the TextWidth method. The width will be in the
same unit of measure that the object uses. By default, the Printer object use twips
(1440 per inch). But you really don't even need to know that. Just compare the
TextWidth of the string with the ScaleWidth of the printer.

Dim lWidth As Long


lWidth& = Printer.TextWidth(txtStory.Text)
'Factor in 10% left and right margins
If lWidth > Printer.ScaleWidth * 0.8 Then
MsgBox "This output will not fit on one line"
End If

We're using a Long here because the value of TextWidth may be out of an Integer's
range. To actually make the output print nicely you're going to need to write a
procedure where you disect the the original string and insert carriage return-line
feed characters where necessary. Take a look at this:

Sub FormatStringAndPrint(sInput As String)

Dim sTemp As String


Dim bSpace As Boolean
Printer.PaperSize = vbPRPSLetter
Printer.ScaleMode = vbInches
Printer.Line (0.75, 0.75)-(7, 9.5), , B
Printer.CurrentX = 1: Printer.CurrentY = 1

Do
If Printer.TextWidth(sInput$) <= Printer.ScaleWidth - 2 Then
sTemp$ = Trim(sInput$): Exit Do
End If

Do
sTemp$ = sTemp$ & Left(sInput$, 1)
sInput$ = Right(sInput$, Len(sInput$) - 1)
Loop While Printer.TextWidth(sTemp$) < Printer.ScaleWidth - 2

Do
If Right(sTemp$, 1) = Chr(32) Then bSpace = True
If Not bSpace Then
sInput$ = Right(sTemp$, 1) & sInput$
End If

81
sTemp$ = Left(sTemp$, Len(sTemp$) - 1)
Loop While Not bSpace

bSpace = False

Printer.Print sTemp$: sTemp$ = ""


Printer.CurrentX = 1
Loop

Printer.Print sTemp$
Printer.EndDoc

End Sub

The size of document is set up and measured in inches. A border is drawn 3/4 inch
from each edge. The left and right margins of this document are 1 inch, so we set
the next output to the correct coordinates.

First, we find out if what we have will fit on one line. If not, the string is transferred
one character at a time to a temporary string. The loop prevents the temp string
from becoming longer than will fit on one line.

Second, we backtrack through the temp string and transfer characters back to the
original string (as long as it is not a space). The loop is terminated when a space is
found. In other words, we get rid of any partial words.

The temp string is then sent to the printer, and the right margin is again adjusted to
1 inch. Upon constructing the last line, we exit the main loop, send the partial line to
the printer and print it out.

This procedure will not work well with strings which have embedded carriage return-
line feed characters. Of course, you could improve this code to remove Chr(13) &
Chr(10) first.

Printing in color

If you want to print in color, set the ColorMode property of the Printer object to a
value of 2 (or use the constant vbPRCMColor) and the ForeColor property to a color
value. If the printer is not color capable, setting the ColorMode property will be
ignored. Set ColorMode to 1 (vbPRCMMonochrome) to switch back to black and
white. Printing in color to Forms and Picture Boxes simply involves setting the
ForeColor property. The code below switches to color printing, sets the color to
green, prints a line, and sets the color back to Monochrome.

Printer.ColorMode = vbPRCMColor
Printer.Forecolor = RGB(0, 255, 0)
Printer.Print "Green text"
Printer.EndDoc
Printer.ColorMode = vbPRCMMonochrome
Fonts

The Printer object has several font properties you can set to give your output some
extra dazzle. Don't set a font which the user doesn't have! The Fonts collection will
contain the names of all the fonts available to the printer.

Private Sub Form_Load()

82
Dim i As Integer
Randomize
For i% = 0 To Printer.FontCount -1
lstFonts.AddItem Printer.Fonts(i%)
Next i%
lstFonts.ListIndex = Int(Rnd * Printer.FontCount)
MsgBox "Select a printer font"
End Sub

Private Sub cmdSetFont_Click()


If lstFonts.ListIndex = -1 Then
MsgBox "No font selected"
Else
Printer.FontName = lstFonts.List(lstFonts.ListIndex)
End If
End Sub

Here we've used a For...Next loop with the FontCount property to add all the
available font names to a List Box, randomly selected a font from the list, prompted
the user to select a font for printing, and assigned the font to the FontName property
of the Printer.

You can set the following properties of the Printer to True or False:

Printer.FontBold = True|False
Printer.FontItalic = True|False
Printer.FontUnderline = True|False
Printer.FontTransparent = True|False
Printer.FontStrikethrough = True|False

Set the size of the font with the FontSize property. The size of a font is measured in
points. There are 72 points to an inch. Maximum point value is 2160.

Printer.FontSize = 36
This sets the FontSize to one-half inch.
More Printer properties and methods
Printer.Copies = (number of copies)

sDeviceName$ = Printer.DeviceName
(Returns the name of printer; example: "HP DeskJet 550C Printer")

sDriverName$ = Printer.DriverName
(Returns the name of the printer driver; example: "DESKJETC")
(This refers to DESKJETC.DRV, the program [driver] which runs the printer)

sPort$ = Printer.Port
(Returns the port which the printer operates through)
(Example: "LPT1:")

Printer.KillDoc
(Terminates the printing operation)

Printer.Orientation = vbPRORPortrait|vbPRORLandscape
(Portrait produces a page 11 inches high by 8.5 inches wide)
(Landscape produces a page 8.5 high by 11 inches wide)
(These measurements don't apply if you've set the PaperSize property)

Printer.PaperSize = (constant)
(There are dozens of constants for this property)
(Set the Height and Width properties for better results)

iPage% = Printer.Page
(Returns what page is being printed or how many
pages have been printed since the last EndDoc method.)

83
Printer.PaperBin = (constant)
(Tells the printer which bin [upper, lower, etc.] to get the paper from)
(There are 14 constants for this property)

Printer.PrintQuality = (dots per inch [dpi])


(You can also use four constants
vbPRPQDraft, vbPRPQLow, vbPRPQMedium, vbPRPQHigh)

Page 27: Graphics Methods


View Page Comments Leave a Comment

Graphics methods are basically for the artistically talented programmer. A knowledge
of geometry goes a long way too. Personally, I can draw a few lines and a few
circles, but I can't actually create anything pleasant to look at. Maybe you'll have
better luck.

Paint event

The Paint event exists solely for drawing graphics on a Form or Picture Box. The
event triggers just after a form is loaded, and every time "repainting" is necessary.
Hidden parts of Forms are not really there and must redraw themselves when they
become exposed again. Windows manages all these "Paint" messages for you. You
simply write the code.

If your Form or PictureBox's AutoRedraw property is set to True, you will not get
Paint events. The graphics drawn will be saved in a persistent image and will be
automatically redrawn when necessary. This persistent image can be accessed
through the Image property. When using AutoRedraw, you'll need to invoke the
Refresh method of the object you're drawing on to actually display any newly drawn
graphics.

Cls method

Cls stands for "clear screen", applies to Forms and PictureBoxes only, doesn't have
any arguments, and doesn't take too much of an explanation: all graphics drawn will
be cleared. Background pictures contained in the Picture property will not be
affected.

Line method

The Line method can draw straight lines, rectangles, and rectangles filled with color.
Set the thickness of the line with the DrawWidth property. You can draw dashed and
dotted lines by setting the DrawStyle property, however, DrawStyle settings will only
work when DrawWidth is set to a value of 1. The color of the line drawn is set with
the ForeColor property. You can experiment with the different settings of the
DrawMode property to achieve different effects. All the properties mentioned here
are properties of the Form (or Picture Box). When writing code in the Paint event you
can omit the name of the Form.

Private Sub Form_Paint()

'Draw a 32 by 32 grid

84
Dim x As Single, y As Single

For x = 0 To ScaleWidth Step ScaleWidth / 32


Line (x, 0)-(x, ScaleHeight)
Next

For y = 0 To ScaleHeight Step ScaleHeight / 32


Line (0, y)-(ScaleWidth, y)
Next

End Sub
Different ways to use the Line method

(x1, y1) are start coordinates and (x2, y2) are end coordinates

'Draws a line using the ForeColor property


Line(x1, y1) - (x2, y2)

'Draws a line in specified color


Line(x1, y1) - (x2, y2), vbRed

'Draws a rectangle using the ForeColor property


Line(x1, y1) - (x2, y2), , B

'Draws a rectangle in specified color


Line(x1, y1) - (x2, y2), vbRed, B

'Draws a color-filled rectangle using the


'FillColor property (B for Box and F for Filled)
Line(x1, y1) - (x2, y2), , BF

'Draws a color-filled rectangle in specified color


Line(x1, y1) - (x2, y2), vbRed, BF

'Do not use F without B!


Line(x1, y1) - (x2, y2), , F
Scaling your Forms/PictureBoxes

By default, Forms and PictureBoxes are measured in twips (about 1440 per inch).
Other coordinate systems are available as well- inches, millimeters, centimeters,
characters (120 twips horizontally, 240 twips vertically), points (72 per inch), and
pixels (actual size of a pixel depends on user's screen resolution). You'll find all these
options under the ScaleMode property. You can also define your own custom scale
(user) by using the Scale method.

This code uses the Line method with the "box filled" option, defines a custom scale
and draws a checkerboard.

Private Sub Form_Paint()

Dim x As Integer, y As Integer


Caption = "Checkerboard"
'Makes the form square
Width = Height
'Define checkerboard scale
Scale (0, 0) - (8, 8)
'Create color-filled rectangles
For x = 0 To 6 Step 2
For y = 0 To 6 Step 2
Line (x, y)-(x + 1, y + 1), vbRed, BF
Line (x + 1, y + 1)-(x + 2, y + 2), vbRed, BF
Line (x + 1, y)-(x + 2, y + 1), vbBlack, BF
Line (x, y + 1)-(x + 1, y + 2), vbBlack, BF
Next y
Next x

85
End Sub
PSet and Point

With these two methods, you can set or get the color of one pixel on the screen. Try
this in Form_Paint where the ScaleMode property is set to 3 - Pixels.

Private Sub Form_Paint()

Dim x As Long, y As Long, i As Long

Cls
For i = 1 To ScaleWidth * 300
x = Int(Rnd * ScaleWidth + 1)
y = Int(Rnd * ScaleHeight + 1)
PSet (x, y), Int(Rnd * 1024 * 1024 * 16)
Next

End Sub
The Point method retrieves the color of one pixel. The code sample below disects the
return value of Point to get red, green, and blue values. To make this work, add a
background picture to a form with 3 text boxes named as below and add the code
into Form_MouseMove.
Private Sub Form_MouseMove(Button As Integer, _
Shift As Integer, X As Single, Y As Single)

Dim lColor As Long


lColor = Point(X, Y)

txtRed = lColor Mod 256


txtGreen = lColor \ 256 Mod 256
txtBlue = lColor \ 65536 Mod 256

End Sub

See also Page3A: Using Colors

Circle method

The Circle method will draw circles, ellipses, arcs, and pie slices. For a simple circle,
you just specify the required arguments: the coordinates of the center of the circle
and the radius.

Circle(ScaleWidth / 2, ScaleHeight / 2), 200

This line of code draws a circle centered on the form with a radius of 200 units. The
units are governed by the ScaleMode property and the border color is specified by
the ForeColor property.

Circle(ScaleWidth / 2, ScaleHeight / 2), 200, vbRed

If you set the FillStyle property to something other than 1 - Transparent, the circle
will be filled with color from the FillColor property or you can override the FillColor by
using the Circle method's optional color argument.

'Blue bordered circle filled with white


ForeColor = vbBlue
FillStyle = vbFSSolid
FillColor = vbWhite

86
Circle(ScaleWidth / 2, ScaleHeight / 2), 200
Drawing arcs and pie slices
Circle(ScaleWidth / 2, ScaleHeight / 2), 600, , 6.28, 4.71
Circle(ScaleWidth / 2, ScaleHeight / 2), 600, , -6.28, -4.71

We need two more arguments to draw arcs and pie slices, start and end. Acceptable
values for start and end are -2 pi to 2 pi, or approximately -6.28 to 6.28. Use
negative values to create pie slices and positive values to create arcs. The second
line of code from above created the filled pie slice below. The Form's FillMode was set
to 0 - Solid and the FillColor was set to a yellow color beforehand.

Using the Line and Circle methods together, along with some smart techniques, we
can draw any kind of polygon that exists. How about a rounded rectangle? Here's a
Sub procedure I wrote which does just that.

Public Sub RoundRectFrame(obj As Object, ByVal X1!, _


ByVal Y1!, ByVal X2!, ByVal Y2!, _
ByVal curve!, Optional ByVal color& = vbBlack)

Const PI = 3.14159

obj.Line (X1, Y1 + curve)-(X1, Y2 - curve), color


obj.Line (X2, Y1 + curve)-(X2, Y2 - curve), color
obj.Line (X1 + curve, Y1)-(X2 - curve, Y1), color
obj.Line (X1 + curve, Y2)-(X2 - curve, Y2), color
obj.Circle (X1 + curve, Y1 + curve), curve, color, PI / 2, PI
obj.Circle (X2 - curve, Y1 + curve), curve, color, PI * 2, PI / 2
obj.Circle (X1 + curve, Y2 - curve), curve, color, PI, PI * 1.5
obj.Circle (X2 - curve, Y2 - curve), curve, color, PI * 1.5, PI * 2

End Sub
With this Sub, pass in as parameters: the object to draw on (Form, PictureBox, or
even Printer), the coordinates of the upper-left corner of the bounding box (X1, Y1),
the lower-right corner coordinates (X2, Y2), the radius of the quarter-circle (arc),
and optionally a border color. Set various properties of the object like DrawWidth,
etc. beforehand and be sure your coordinates are compatible with the ScaleMode.
Aspect ratio
Circle(ScaleWidth / 2, ScaleHeight / 2), 600, , -6.28, -4.71, 0.4

87
Yes, there are more arguments, this one is the Aspect ratio which will create elliptical
or oval shapes. The default is 1 (perfect circle). Set this value to something less than
1 to stretch the circle lengthwise and higher than 1 to stretch it heightwise. Use a
value greater than zero or you might get unpredictable results. The line of code
above created the pie slice below.

Remember, if you omit some arguments, you must still use the comma separator.

Circle(ScaleWidth / 2, ScaleHeight / 2), 600, , , , 0.4

Here we've created an oval and using the default color, start, and end arguments.

You might try creating some simple animations by slightly changing the aspect ratio
on the fly (in a loop or timer). The code below use a Timer control and creates a
rotating circle.

Private Sub tmrCircle_Timer()

'Form ScaleMode is twips


'Set the Form's DrawWidth, FillStyle, FillColor beforehand
'Timer's Interval property set to 100 milliseconds
Static sngAspect As Single
Dim sngIncrement As Single
Static bReverse As Boolean

sngIncrement = IIf(bReverse, -0.06, 0.06)


sngAspect = sngAspect + sngIncrement
If sngAspect < 0 Or sngAspect > 2 Then bReverse = Not bReverse
Cls
Circle (2000, 2000), 1000, vbRed, , , sngAspect

End Sub
SavePicture statement

This statement saves your graphic output to a bitmap (.BMP) file. The best way to do
this is to set AutoRedraw to True and use the Image property along with a string file
name.

SavePicture picGraphics.Image, App.Path & "\saved.bmp"

Afterwards, you can convert the picture file to a compressed format with Paint Shop
Pro or another graphics program.

ClipControls property

If you have a long, complicated series of graphics methods in the Paint event, you
might want to set this property to False. When you do this, Windows will only repaint
the necessary parts of the object (the region which is newly exposed), thereby
speeding up the painting process.

88
Page 28: Error Handling
View Page Comments Leave a Comment

There's no sense trying to avoid it, your program is going to make errors. You can't
physically make your users select the right options at the right time or prevent them
from entering invalid input.

Enabling and disabling features

Provide a visual clue to the user by disabling controls (Enabled = False) when you
don't want them accessed. An example of this would be the Cut, Copy, and Paste
menu items which you see in just about every application. If there is no text on the
clipboard, then Paste will be disabled. If there is no highlighted text, Cut and Copy
are disabled.

Testing for proper values and data types

When you are accepting input from the user, you can test it for valid values and also
check to make sure it's the right data type. The example below requires the user to
enter a number between 101 and 200.

Dim response As Variant


response = InputBox("Enter a number")
If IsNumeric(response) Then
'User correctly entered a number
If response > 100 And response < 201 Then
'User entered a number in the correct range
End If
Else
'User entered a non-numeric value
End If

Besides the IsNumeric boolean function, there's also:

• IsDate(expression) - checks to see if an expression can be converted to the


Data data type.
• IsEmpty(variable) - returns True when a variable has not been intialized.
• IsArray(variable name) - see if a variable is actually an array
• IsNull(expression) - returns True if expression is Null. Null is a value which
goes one step further than Empty, a state of non-existence

Debug object

A big part of debugging your code will be testing your variables, properties, and
formulas for the value you're expecting them to have. One quick way to do this
would be to put a MsgBox statement in the right place. This will halt the code, show
you a value or two, and then continue on. If showing the modal MsgBox causes
problems, you can print values to the Debug window, now called the Immediate
window in VB 5.0.

Debug.Print SomeValueToPrint

89
After you end your program, you can inspect what was printed to the Debug window
by selecting View/Immediate Window from the menu or keypressing Ctrl + G.

Debug.Assert, Stop statement and Breakpoints

While debugging, you may find the need to put program execution into break
mode. You can make sure that code is working properly up to a certain point, and
then adjust the break mode accordingly.

The Stop statement will put you in break mode, but I don't recommend using it
because, unlike calls to the Debug object and setting breakpoints, the Stop
statement gets compiled into your executable and virtually acts as an End statement.

To set a breakpoint, look at the far left side of any code window where there is a
grey margin and click next to the line of code where you want to break. The code on
that line will be highlighted, by default, in a rust color, and a small circle will appear
also.

Using the Assert method of the Debug object is like using a breakpoint in an If-Then-
Else scenario. You supply an True/False expression as the one argument of the
method. If the expression is True, execution continues, if False, you enter break
mode.

Debug.Assert iTempVar = 24
On Error Statement

Once you've debugged your code and done all you can to prevent errors with the
above methods, you'll need to trap any other unexpected errors, and optionally
inform the user of what happened in order to correct the situation. If you do not trap
errors, your program will crash, leaving the user wondering what kind of
programmer you are. The On Error statement can be used a variety of ways to trap
errors.

On Error Resume Next

This one line of code translated into English might say, "Upon finding an error, ignore
it, and resume executing code with the next line." On Error statements are placed in
the beginning of a procedure with any Dim statements.

Dim sFileName As String


sFileName$ = InputBox("Enter the name of the file to create")
CreateTextFile sFileName$

Sub CreateTextFile(sFile As String)


On Error GoTo errorhandler
Open sFile$ For Output As #1
Close #1
Exit Sub
errorhandler:
MsgBox Err.Description, vbCritical, "CreateTextFile"
End Sub

This sub procedure creates a blank text file with a string entered by the user. If the
input is not a valid name for a file, an error occurs and program execution branches
to the line label where a description of the error is displayed to the user. To create

90
line labels, use the variable naming convention and add a colon to the end. Note that
Exit Sub precedes the line label to prevent the error handling code from executing
when the procedure is error-free.

Err object

When an error does occur, the properties of the built-in Err object will be populated
with information about the error. Besides Err.Description seen above, you can get
the error number with Err.Number.

MsgBox "Error number " & Err.Number & vbCrLf _


& Err.Description, vbCritical, "CreateTextFile"

Error descriptions are more useful to the user than error numbers.

If you'd like to continue with the procedure after informing the user of the error, you
can use Resume in the error handling routine in one of three ways:

errorhandler:
MsgBox Err.Description, vbCritical, "CreateTextFile"
Err.Clear
Resume 'Goes back to the line which caused the error

errorhandler:
MsgBox Err.Description, vbCritical, "CreateTextFile"
Err.Clear
Resume Next 'Goes to the line after the line which caused the error

errorhandler:
MsgBox Err.Description, vbCritical, "CreateTextFile"
Err.Clear
Resume (line label) 'Goes directly to a line label

Before resuming code, you must clear out the old error information with the Clear
method of the Err object. This sets Err.Number to 0 and Err.Description to an empty
string. The Err object will automatically clear itself at the end of a procedure (End
Sub, End Function, etc.)

On Error GoTo 0

Using the On Error GoTo (line label) statement enables an error handling routine in
the procedure. You may also use an On Error GoTo 0 statement to transfer error
handling to a higher level procedure. If no error handling is found there, VB will
continue moving to higher level procedures until an error handler is found. You might
term this as "passing error handling up the call stack".

Let's say your Form_Load event contains a call to Sub called Procedure1 and error
trapping is enabled...

Private Sub Form_Load()

On Error GoTo errhandler

Procedure1

(lines of code)

91
Exit Sub

errhandler:

MsgBox Err.Description

End Sub

...and you want Form_Load to handle errors for both routines.

Private Sub Procedure1()

On Error GoTo 0

(lines of code)

End Sub
Erl function

Erl is for programmers who use line numbers and want to know what line the error
occurred on.

'Create an array of the next 10 years.


'Causes a 'Subscript out of range' error on Line 40
10: On Error GoTo 70
20: Dim iYears(1 To 10) As Integer
30: For i = Year(Now) + 1 To Year(Now) + 10
40: iYears(i) = i 'Correct: iYears(i - Year(Now)) = i
50: Next
60: Exit Sub
70: MsgBox "Error on Line: " & Erl & " (" & Err.Description & ")"
Note: I am calling Erl a function because that's how it appears to be used. Erl is
semi-documented as a property of the Err object, but you won't find any evidence of
that and using Err.Erl will raise an 'Object doesn't support this property or method'
error.
Error function

You can obtain the full list of error descriptions and numbers in your help file (MSDN
library). You can also get this information programmatically with the Error function.
Draw a ListBox or ComboBox and use code something like this to populate it.

Dim i As Integer
For i = 1 To 1024
If Error(i) <> "Application-defined or object-defined error" Then
lstErrDesc.AddItem "Error #" & i & " - " & Error(i)
End If
Next

Page 29: Distributing Your Project


View Page Comments Leave a Comment

Your project is done and you want to show it to the world. It's time to compile your
code. Compiling means to change what you've done into the 1's and 0's that a
computer understands. VB comes with a file named C2.EXE which is your compiler,
sometimes called a translator - a program which translates the higher-level Visual
Basic language into the lower-level machine language.

92
If you're designing an application for your own personal use, you may never have a
need to compile, since VB itself is capable of compiling and running a project in it's
own environment - the Integrated Developement Environment (IDE). Running a
project in the IDE is virtually the same as running a compiled executable file.

Making your application work correctly on other computers is another ballgame. The
first step to dealing with this is to create the executable (.EXE) file. This is very easy
to do, just select Make ?.EXE from the File menu. If you've neglected to actually give
your project a name by this time, do so before making the .EXE, no one will be
impressed by Project1.EXE. Go to Project/Project1 Properties and enter a descriptive
name in the Project Name field and while you're there set the version number, the
application title and an icon. You can only select an icon which has already been
assigned to the Icon property of one of your Forms.

Now you've got an executable file, which will run in all normal Windows ways, like
double-clicking it's icon, typing the file name at the DOS prompt, or using Run from
the Windows start button.

Ok, now you're going to upload that executable to your friends so they can check out
your work, right? Wrong. Your program is going to need a lot of support. Depending
on the functions of your program, you will need to include some run-time files with
your executable, custom control files, and any external data files like readme.txt or
maybe a database. It's possible that your small executable will have up to 3 or 4
megs worth of these dependent files.

Run-time files are special executables with a .DLL (dynamic link library) extension.
These DLL's contain the code for all that great VB functionality. The methods and
functions of intrinsic controls are included in the DLL's, but not any custom controls
which you added during the design of your application. Intrinsic controls are the ones
included in the default Toolbox.

Required files

At a minimum, a program written with VB 5.0 Professional Edition needs the


following files to run properly.

• MSVBVM50.DLL (843K)*
• OleAut32.DLL (315K)*
• AsycFilt.DLL (74K)*
• VB5StKit.DLL (16K)*
• OlePro32.DLL (16K)*
• Ctl3D32.DLL (15K)*
• ComCat.DLL (10K)*
• StdOle2.TLB (7K)*
• Setup.EXE (88K)
• Setup1.EXE (72K)*
• Setup.LST (7K)
• St5Unst.EXE (37K)*
• Your.EXE (?K)
• * - Compressed size
• Total 13 files (1.46 Mg)

93
These 13 files could be compressed a little further with WinZip to about 1.35 Mg.
Now this is the bare minimum for a simple program to setup, run, and optionally
uninstall, so you shouldn't plan on getting the whole deal on one floppy.

(Required files are different for VB 6.0, but still add up to about 1.4 Mg.)

Windows 98 ships with these files. Your program should run on Win98 without them,
but if you want to be 100% sure, include them; leaving them out is taking an
unnecessary risk.

Application Setup Wizard/Package & Deployment Wizard

Luckily, all the hard work of figuring out what is needed to make your program run
properly on other computers has been done by the Application Setup Wizard (in VB
5.0). This wizard has been enhanced and renamed the Package & Deployment
Wizard (PDW) in VB 6.0. These wizards will create a fairly nice looking setup
program (setup.exe) and create installation floppy disks or set everything up in a
single directory for you.

Make the .EXE and close your project before opening a wizard. All you have to do is
tell the Wizard where your project file (.VBP) is located and perform simple tasks
along the way. The wizards will detect all dependent files which should be included in
the setup process, the only manual task is for you to add the external files. Anyone
who's computer-literate can now install your application on their machine by running
setup.exe. The Setup program will add shortcut(s) to the user's Start menu. In VB
5.0, your program's name and it's icon will appear in the Program Files menu. In VB
6.0, the PDW has a special screen where you can instruct the setup program to
create additional Start menu items such as a shortcut to your ReadMe file or your
home page on the Internet. An uninstall program is automatically included. Your
program can easily be removed through the user's Control Panel.

Installing to sub-directories

If you keep your external files in sub-directories, you'll have to tell the Wizard to
create them.

In VB 5.0
After you add a file to the setup, click the File Details button. Change the destination
directory from $(App.Path) to $(App.Path)\Images or $(App.Path)\Sounds or
whatever the name of your sub-directory is.

In VB 6.0
PDW has a special screen for creating destination directories. All of the files you
added will be listed. Just change $(App.Path) like above.

Modify the setup program

The source code for the setup program is included with VB. The project is named
Setup1.vbp. If you'd like to be an installation expert, this would be a good project to
start learning by. Most of the code is contained in Form_Load in the startup Form
(frmSetup1).

94
Setup.lst is also an important file in the installation process. This is a straight text
INI file which can be customized.

Page 30: System Objects


View Page Comments Leave a Comment

This page will deal with three 'system' objects which can make your programming
life much easier. The App object holds information about your program as a whole
and where it's located on the user's machine. The properties of the Screen object will
help you make your program look pleasant on different monitors and resolution
settings, and the Clipboard object allows cut, copy, and paste fucntionality.

App object

You must allow your users to install your program in the directory of their choice
during setup. Finding out their drive letters and directory structure is not necessary.
The Path property of the App object will return the location (directory) of your
program. Your executable file, and any external data, picture, or sound files will be
also be in this location. (Unless your setup program is totally inept.) To access these
files, you'll concatenate the file name and extension to App.Path

Now don't go jumping the gun and get started on this, because App.Path has a quirk.
If your program gets installed on a root directory, App.Path will contain a trailing
backslash.

'A root directory


C:\MyProgram

'Not a root directory


C:\Program Files\MyProgram

To account for this, use the follwing function which checks for a backslash and adds
one if necessary.

Public Function GetEXEPath() As String


If Right$(App.Path, 1) = "\" Then
GetEXEPath$ = App.Path
Else
GetEXEPath$ = App.Path & "\"
End If
End Function

This function is in every application which I write.

Here's an example which loads a text file into a TextBox.

Dim h As Integer
h = FreeFile
Open GetEXEPath$ & "readme.txt" For Input As #h
txtMain.Text = Input(LOF(h), h)
Close #h

95
Another useful property of the App object is PrevInstance. The property returns
True if one or more instances of your program are already running. Programmers
who decide that running multiple instances of their program is not appropriate can
prevent it with the following code in the first Form shown.

Private Form_Load( )
If App.PrevInstance Then
Unload Me
End If
End Sub

The rest of the App object's properties are less useful.

• The EXEName property returns the name of your executable file without the
extension. At design time, this property returns the name of the project.
• The Title property might be useful in a message box.

Msgbox "Some message", vbCritical, App.Title

All of the App objects properties are read only at run time. One way or another, you
will set these properties at design time, many of which are accessed through the
Project/Properties dialog box. You should go to this dialog box just before compiling
your finished project and set appropriate values for the following App object
properties.

• Title
• ProductName
• Comments
• CompanyName
• FileDescription
• LegalCopyright
• LegalTrademarks
• Major, Minor, and Revision (Version)

Screen object

The Screen object represents the Desktop.

The Height and Width properties will return the dimensions of the screen in twips.
The actual numeric values of these properties are not so important. You could use
these properties to position controls in the correct places when a Form resizes or
maximizes.

'Center a label when the Form maximizes


Private Sub Form_Resize()

If WindowState = vbMaximized Then


lblHeading.Left = (Screen.Width / 2) - (lblHeading.Width / 2)
lblHeading.Top = (Screen.Height / 2) - (lblHeading.Height / 2)
End If

End Sub

96
Set the MousePointer property to an hourglass (or any other icon or cursor) just
before running code which will take a second or more. After the code runs, set the
property back to the user's default MousePointer.

Screen.MousePointer = vbHourglass
'Long drawn out code
Screen.MousePointer = vbDefault

The Screen object's MousePointer and MouseIcon properties work identically to any
other object's MousePointer and MouseIcon. To use a custom icon or cursor, set the
MousePointer property to 99, and then set the MouseIcon property to the path of a
valid picture file or Picture property of another object.

The FontCount property returns the number of Screen Fonts available. This has
nothing to do with Fonts available to the Printer object. One use of this property is to
load a ComboBox with all the Font names. We'll have to use the Fonts property in
this example as well.

Dim i As Integer
For i% = 0 To Screen.FontCount - 1
cboFonts.AddItem Screen.Fonts(i%)
Next i%

The TwipsPerPixelX and TwipsPerPixelY properties can be used to convert twips


to pixels.

Dim x, y
x = Screen.Width / Screen.TwipsPerPixelX
y = Screen.Height / Screen.TwipsPerPixelY
MsgBox "Your screen resolution is set to " & x & "x" & y

Converting the Screen's Width and Height is also useful for certain API calls which
require measurements in pixels.

There are many times when you need to know what control currently has the focus.
You can use the ActiveControl property for this.

If Screen.ActiveControl.Name = "cboFonts" Then...

If TypeOf Screen.ActiveControl Is TextBox Then...

If Screen.ActiveControl.Tag = "IsArray" Then...

If you have controls which are part of a control array, you might want to set values
to each of the Tag properties to differentiate between them since they'll all have the
same name.

The ActiveForm property might be used in an MDI application where you need to
determine which child form is active.

Screen.ActiveForm.PrintForm

Unload Screen.ActiveForm

Screen.ActiveForm.WindowState = vbMinimized
Clipboard object

97
The Clipboard object represents the Windows clipboard which is available to all
running applications, therefore you can allow your users to place text or pictures on
the Clipboard and paste them anywhere they like.

Setting up menu items for cut, copy, and pasting from and into one TextBox is fairly
simple.

Private Sub mnuCopy_Click( )


Clipboard.Clear
Clipboard.SetText txtMain.SelText
End Sub

Private Sub mnuCut_Click( )


Clipboard.Clear
Clipboard.SetText txtMain.SelText
txtMain.SelText = ""
End Sub

Private Sub mnuPaste_Click( )


txtMain.SelText = Clipboard.GetText
End Sub

As you can see, we use the Clear method before placing any text on the Clipboard,
then the SetText method puts the selected (highlighted) text from the TextBox on
the Clipboard. To paste, we retrieve the text from the Clipboard with the GetText
method and place it wherever the insertion point is.

If you have more than one TextBox which could have selected text, simply use
Screen.ActiveControl instead of a specific TextBox name.

Private Sub mnuCopy_Click( )


Clipboard.Clear
Clipboard.SetText Screen.ActiveControl.SelText
End Sub

To cut, copy, and paste pictures, use the SetData and GetData methods. The
following example checks to see if the ActiveControl is a Picture Box and if so, copies
its picture to the Clipboard.

If TypeOf Screen.ActiveControl Is PictureBox Then


Clipboard.SetData Screen.ActiveControl.Picture
End If
Retrieving a picture:
imgUpper.Picture = Clipboard.GetData

The Clipboard can actually have up to five separate pieces of data as long as each
piece of data is in a different format. These five formats are Text, Bitmap, Metafile,
DIB, and Color Palette. You can use the GetFormat method to check the Clipboard
for the existence of data in a specific format. You'll need to supply the correct
constant.

If Clipboard.GetFormat(vbCFBitmap) Then...
If Clipboard.GetFormat(vbCFMetafile) Then...
If Clipboard.GetFormat(vbCFDIB) Then...
If Clipboard.GetFormat(vbCFPalette) Then...

You may have noticed applications which gray out the paste option in the menu
when there is no text on the Clipboard. Here's how it's done:

98
Private Sub mnuEdit_Click( )
If Clipboard.GetFormat(vbCFText) Then
mnuPaste.Enabled = True
Else
mnuPaste.Enabled = False
End If
End Sub

You can further improve the click event for the Edit menu by checking the TypeOf
Screen.ActiveControl and the Clipboard for the existence of graphics. Disable cut and
copy if the ActiveControl doesn't support it.

Here's a whole pile of code which allows cut, copy, and paste with TextBoxes,
ListBoxes, ComboBoxes, and PictureBoxes.

Private Sub mnuEdit_Click()


mnuCut.Enabled = True
mnuCopy.Enabled = True
mnuPaste.Enabled = False
If TypeOf Screen.ActiveControl Is TextBox Or _
TypeOf Screen.ActiveControl Is ComboBox Or _
TypeOf Screen.ActiveControl Is ListBox Then
If Clipboard.GetFormat(vbCFText) Then mnuPaste.Enabled = True
ElseIf TypeOf Screen.ActiveControl Is PictureBox Then
If Clipboard.GetFormat(vbCFBitmap) Then mnuPaste.Enabled = True
Else
'Can't cut or copy from ActiveControl
mnuCut.Enabled = False
mnuCopy.Enabled = False
End If

End Sub

Private Sub mnuCopy_Click()


Clipboard.Clear
If TypeOf Screen.ActiveControl Is TextBox Then
Clipboard.SetText Screen.ActiveControl.SelText
ElseIf TypeOf Screen.ActiveControl Is ComboBox _
Or TypeOf Screen.ActiveControl Is ListBox Then
Clipboard.SetText Screen.ActiveControl.Text
ElseIf TypeOf Screen.ActiveControl Is PictureBox Then
Clipboard.SetData Screen.ActiveControl.Picture
End If
End Sub

Private Sub mnuCut_Click()


'Do same as copy, then clear ActiveControl
mnuCopy_Click
If TypeOf Screen.ActiveControl Is TextBox Then
Screen.ActiveControl.SelText = ""
ElseIf TypeOf Screen.ActiveControl Is ComboBox _
Or TypeOf Screen.ActiveControl Is ListBox Then
Screen.ActiveControl.Text = ""
ElseIf TypeOf Screen.ActiveControl Is PictureBox Then
Screen.ActiveControl.Picture = LoadPicture("")
End If
End Sub

Private Sub mnuPaste_Click()


If TypeOf Screen.ActiveControl Is TextBox Then
Screen.ActiveControl.SelText = Clipboard.GetText
ElseIf TypeOf Screen.ActiveControl Is ComboBox Then
Screen.ActiveControl.Text = Clipboard.GetText
ElseIf TypeOf Screen.ActiveControl Is ListBox Then
Screen.ActiveControl.AddItem Clipboard.GetText
ElseIf TypeOf Screen.ActiveControl Is PictureBox Then
Screen.ActiveControl.Picture = Clipboard.GetData
End If

99
End Sub

Page 31: Drag and Drop


View Page Comments Leave a Comment

Most of the intrinsic controls can dragged and dropped. Exceptions to this are the
Line and Shape controls and of course the Timer. Many custom controls (.OCX's) can
be dragged and dropped as well, simply check to see if they have DragMode and
DragIcon properties.

Two terms you need to be familiar with are Source and Target. The source is the
control being dragged and the target is the Form or control where it is being
dropped.

The minimum requirements for implementing drag and drop are:

• Set the control's DragMode property to 1-Automatic at design time or to


vbAutomatic at run time.
• Add the following code:

Private Sub Form_DragDrop(Source As Control, X As Single, Y As Single)

Source.Move X, Y

End Sub

There are a few drawbacks to using the simple method described above.

• The object will not drop where the drag outline indicates it will, but rather it
drops below and to the right of the mouse position.
• Controls will not receive MouseDown, MouseMove, and MouseUp events when
DragMode is set to Automatic.

For a better looking drag and drop, use the default 0-Manual setting. You'll need to
initiate the drag action by using the Drag method in the source control's MouseDown
event. In order to make the control drop where the drag outline is, you'll have to
record the X and Y locations of where the drag was initiated within the source control
and then factor those coordinates into the Move method in the DragDrop event of
the Form or any other control you're allowing the source to be dropped on.

'General Declarations
Dim dx As Single, dy As Single

Private Sub imgFile_MouseDown(Button As Integer, _


Shift As Integer, X As Single, Y As Single)

dx! = X!: dy! = Y!


imgFile.Drag

End Sub

Private Sub Form_DragDrop(Source As Control, X As Single, Y As Single)

Source.Move X! - dx!, Y! - dy!

100
End Sub

In this example, the Drag method was used without any arguments which by default
will initiate the drag. It could also be written as:

imgFile.Drag vbBeginDrag

The Drag method has two other constants which can be used- vbEndDrag and
vbCancelDrag- however, it's not necessary to specifically end the dragging action in
the MouseUp event unless you are allowing the user to drag and drop with keyboard
events or some other method. When the mouse is released after dragging has
begun, a DragDrop event will trigger in whichever object the mouse is over.

Let's say there was a command button (cmdNext) on the same Form as imgFile and
you wanted to inform the user that this was not a valid place to drop the imgFile
control.

Private Sub cmdNext_DragDrop(Source As Control, X As Single, Y As Single)

MsgBox "File cannot be dropped here"

End Sub

What you actually do with the DragDrop events will be peculiar to your own project.
Maybe you're manipulating files or simply moving images.

DragOver event and the State argument

After a drag operation has begun, you won't get MouseUp, MouseMove, or
MouseDown events as usual. The DragOver event exists to compensate for that. In
addition to having the X and Y location of the mouse at your disposal, the State
argument will tell you if the control being dragged has entered into, left, or moved
within a target. Remember that, similar to the MouseMove event, this event can
trigger several times per second.

To give you idea of what this event is doing, enter this code into the Form's
DragOver event...

Select Case State


Case 0: Print Source.Name; " has entered "; Me.Name
Case 1: Print Source.Name; " has left "; Me.Name
Case 2: Print Source.Name; " was moved within "; Me.Name
End Select

...and this code into the cmdNext control. Modify the code and place it into any other
control you want to monitor.

Private Sub cmdNext_DragOver(Source As Control, _


X As Single, Y As Single, State As Integer)

Select Case State


Case 0: Print Source.Name; " has entered cmdNext"
Case 1: Print Source.Name; " has left cmdNext"
Case 2: Print Source.Name; " was moved within cmdNext"
End Select

End Sub

101
You'll use the DragOver event to control or limit the dragging operation, or to give
the user visual hints, such as changing the MousePointer, colors or styles of targets.

Page 32: Dates and Time


View Page Comments Leave a Comment

Before trying to manipulate dates and time, you should know that dates are
stored as serial numbers where:

• 0 represents Dec. 30, 1899


• 1 represents Dec. 31, 1899
• 2 represents Jan. 2, 1900, etc.

Whoever decided that time would begin on Dec. 30, 1899, I don't know, but this
doesn't mean you can't use dates prior to this because you can use negative values
as well:

• -1 represents Dec. 29, 1899


• -2 represents Dec. 28, 1899
• -3 represents Dec. 27, 1899, etc.

Any decimal portion of the serial number represents the time of day. For instance:

• 2.5 would be Jan. 1, 1900 12:00:00 PM


• 2.25 would be Jan. 1, 1900 6:00:00 AM
• I am writing this at 36355.684

Kind of like stardates, huh?


Now function

Serial numbers don't mean anything to the user so they must be formatted into
some kind of standard way of displaying dates. Windows will do this automatically for
you when you use the Now function.

Now takes no arguments and accesses the current system time. Although internally
stored as a serial number, the result of Now will be a string formatted according to
the user's Control Panel.

MsgBox "The current date and time is " & Now

If you want to use a specific date/time format for displaying the system time, use
the Format function.

MsgBox "The current date and time is " & _


Format(Now, "dddd mmmm d yyyy")
Date function

102
The Date function is similar to Now but does not return the time portion. The
formatting is also done automatically, but not by the Control Panel. This function
always returns a 10-character string (mm-dd-yyyy). In other words, these two lines
of code are equivalent:

MsgBox "The current date is " & Format(Now, "mm-dd-yyyy")


MsgBox "The current date is " & Date
Time function

The Time function returns the time portion of Now and automatically formats it as an
8-character string (hh:mm:ss). These two lines of code are equivalent:

MsgBox "The current time is " & Format(Now, "hh:mm:ss")


MsgBox "The current time is " & Time

This function is useful for adding a real-time clock to a Form. Draw a Label (lblClock)
and a Timer (tmrClock). Set the Timer's Interval property to 500 (half second) and
add this code:

Private Sub tmrClock_Timer()


If lblClock.Caption <> Time Then
lblClock.Caption = Time
End If
End Sub
Date and Time statements

You can set the system date with the Date statement or the system time with the
Time statement

Date = "7/14/99"
Time = "11:22:00 AM"

You would normally use a string date or time expression in one of the accepted
formats, but a serial number would work as well.

IsDate function

The IsDate function checks to see if an expression (or variable or property) is


capable of representing a date or time. Both numeric and string expressions can
qualify. This is a Boolean function which returns True if the expression can be
converted to the Date data type.

If IsDate(2603.852) Then
'IsDate Returns True
End If

If IsDate("3/4/62") Then
'IsDate Returns True
End If

If IsDate(App.Path) Then
'IsDate Returns False
End If
More Date and Time functions

The following functions will return certain parts of a date. You supply one argument,
the date expression, and the function will return the numeric value you're looking

103
for. If the argument doesn't qualify as a date, you'll get a "Type Mismatch" error, so
you might want to use IsDate on the expression first.

'(The decimal 0.324 represents 7:46:34 AM)

'Second function returns an integer between 0 and 59


iResult% = Second(0.324) 'iResult% = 34

'Minute function returns an integer between 0 and 59


iResult% = Minute(0.324) 'iResult% = 46

'Hour function returns an integer between 0 and 23


iResult% = Hour(0.324) 'iResult% = 7

'(The number -45103 represents July 4, 1776)

'Day function returns an integer between 1 and 31


iResult% = Day(-45103) 'iResult% = 4

'Weekday function returns an integer between 1 and 7


'where 1 is Sunday and 7 is Saturday
'Use the built-in VB constants instead
'vbSunday, vbMonday, vbTuesday, etc.
If Weekday(-45103) = vbThursday Then
MsgBox "This country was born on a Thursday"
End If

'Month function returns an integer between 1 and 12


iResult% = Month(-45103) 'iResult% = 7

'Year function returns an integer between 100 and 9999


iResult% = Year(-45103) 'iResult% = 1776
Two new functions in VB 6.0

The Weekday function is somewhat poor in that it returns an integer corresponding


to a day of the week rather than a string (which is probably what you want from it).
The new WeekdayName function replaces the If or Select Case statements which
need to be written for the return value of the Weekday function.

MsgBox "Today is " & WeekdayName(Weekday(Now)) & vbCrLf & _


"Tomorrow is " & WeekdayName(Weekday(Now + 1))
You can also get the 3 letter abbreviation by setting the second (optional) argument
to True.
MsgBox "Today is " & WeekdayName(Weekday(Now), True)
Like a calendar, VB sees the first day of the week as Sunday. If you want the
WeekdayName function to treat Monday as the first day of the week (value of 1),
you could add the constant vbMonday as the third argument:
The new MonthName function works about the same. It takes a value from 1 to 12
and returns you the name of the month as a string, abbreviated if necessary:
'Random abbreviated month
MsgBox MonthName(Rnd * 12 + 1, True)
Note: Unlike other languages, VB coerced the formula Rnd * 12 + 1 to a whole
number. It's things like this that make VB the best solution for Rapid Application
Development (RAD)
DatePart function

You can also try the DatePart function instead of any of the above functions. This
function has 2 arguments, the first being a string corresponding to what part of the
date you want returned and the other being the date expression. The DatePart
function can also return the quarter, the day of the year, and the week of the year.

104
'Returns an integer between 1 and 4
iResult% = DatePart("q", -45103) 'iResult% = 3

'Returns an integer between 1 and 366


iResult% = DatePart("y", -45103) 'iResult% = 186

'Returns an integer between 1 and 53


iResult% = DatePart("ww", -45103) 'iResult% = 27
Other acceptable strings to use for the first argument are:

• "yyyy" - identical to using Year function


• "m" - identical to using Month function
• "d" - identical to using Day function
• "w" - identical to using Weekday function
• "h" - identical to using Hour function
• "n" - identical to using Minute function
• "s" - identical to using Second function

If you're curious, the lowest serial number which can be used is -657434 (Jan. 1,
100) and the highest is 2958465 (Dec. 31, 9999). VB doesn't deal with dates before
100 AD or after 10,000 AD, so if for some reason you want to work with BC or
futuristic dates (solve the Y10K problem?) it can't be done unless you use your brain
and come up with a nifty formula.

DateSerial function

If you need to know what the serial number of a particular date is you can use the
DateSerial function. You supply the year, month, and day. Like the Date and Time
functions the return value will be automatically formatted, so use the Format
function in conjunction with the DateSerial function to force a numeric display. This
example tells you the serial number of the day JFK was assassinated.

MsgBox Format(DateSerial(1963, 11, 22),"general number")


TimeSerial function

The TimeSerial function will return the decimal which corresponds to a particular
time of day. You'll supply the hour, minute, and second. Again, the return value is
automatically formatted, so we need the Format function. This example returns the
decimal which represents 2:32 PM and rounds it to 4 decimal places at the same
time.

MsgBox Format(TimeSerial(14, 32, 0),".####")


DateDiff function

The DateDiff function can tell you the difference between two dates. Not just the
number of days, but any date or time interval. There are three required arguments,
the string corresponding to the interval (these are the same strings listed above for
use with the DatePart function), and the two dates to compare. Here's an example
which uses DateDiff, Now, and DateSerial to give the user a "countdown to the year
2000" message.

MsgBox "There are " & DateDiff("d", Now, _


DateSerial(2000, 1, 1)) & " days until the year 2000"
DateAdd function

105
The DateAdd function can add or subtract date or time intervals to a particular date.
The first argument is the string which represents the interval (same strings as
DatePart and DateDiff), the second is the number of intervals to add or subtract
(positive numbers for future dates, negative numbers for past dates), and the third
is the date to perform the calculation on. The below example produces a date one
month prior to today.

MsgBox DateAdd("m", -1, Now)

The DateAdd function is very intelligent. It knows about leap years and it knows that
all months don't have the same number of days. So you can be sure that if you're
trying to find the date one month after Jan. 31, the function will return Feb. 28 on
non-leap years and Feb. 29 on leap years.

Page 33: MDI Forms


View Page Comments Leave a Comment

MDI stands for Multiple Document Interface. An MDI Form acts as a container for
Child Forms. Your project can have only one MDI Form, but any number of Child
Forms and non-Child Forms. Many familiar programs are MDI Applications, such as
America Online and the Microsoft Office applications.

You cannot print or draw graphics on an MDI Form. The Picture Box is the only
intrinsic control which can be drawn directly on an MDI Form. Other controls would
have to be contained within the Picture Box. Usually, the purpose of this would be to
create Toolbars and StatusBars without using a custom control.

You must specifically add the MDI Form to your project. Go to Project/Add MDI Form
or right-click in Project Explorer and select Add/MDI Form. Create Child Forms from
any regular Forms by setting the MDIChild property to True.

When a Child Form is minimized, it will be reduced to an Icon at the bottom of the
MDI Form and will not show in the Windows Task Bar. If maximized, it will only
enlarge to the size of the MDI Form client area.

AutoShowChildren property

When you Show a Child Form, the MDI Form is loaded automatically to contain it.
When you Show the MDI Form, a number of things can happen depending on your
code and the AutoShowChildren property.

You already know that Showing any Form loads it and displays it. You should also be
aware that if you reference a Form in code (for instance, setting a property), it is
Loaded but not Shown. By default, the AutoShowChildren property is set to True,
which means if Child Forms are Loaded, they will also be Shown. Setting this
property to False will cause Child Forms to act normally, in other words, they won't
be Shown until you say so with the Show method.

106
Most likely, your MDI Form will run full screen. You can set the WindowState
property to vbMaximized or use the following code in the MDIForm_Load event.

Width = Screen.Width
Height = Screen.Height

Next, you must think about the initial size and location of the Children. If you're just
Showing one Child at a time, then you might just maximize it and allow the user to
minimize and restore. To show multiple Children in a pleasing way, you can set their
Left, Top, Width, and Height based on the Width and Height of the MDI Form, or you
can use the Arrange method.

The Arrange method let's you cascade and tile the Children. Minimized Children will
not be affected by cascading or tiling.

MDIForm1.Arrange vbCascade
MDIForm1.Arrange vbTileHorizontal
MDIForm1.Arrange vbTileVertical
MDIForm1.Arrange vbArrangeIcons

vbArrangeIcons is used to condense the minimized Children at the bottom of the MDI
Form, filling in any gaps which might have occurred from the actions of the user.

When a Child Form is active (the ActiveForm), it's menu, if any, will replace the
menu of the MDIForm. The NegotiateMenus property does not apply to MDI Forms,
and the NegotiatePosition property of a Menu control will have no effect.

Creating a Window menu for an MDI Form is very simple. In the Menu Editor, create
a top-level Menu and check the WindowList box. Don't bother creating any sub-
menus, a list of all open Child Forms will be added automatically with a check mark
next to the ActiveForm. If a minimized Child is selected from the Window menu, it
will be restored.

Unlike regular Forms, MDI Forms can have scroll bars. If any of the Child Forms
extend beyond the client area, scroll bars will automatically be added. If you don't
want them, set the ScrollBars property to False.

Creating New Child Forms at Run-Time

Your MDI Application might allow the user to create new documents. In order to do
this, you'll create a Child at design-time which will act as a template. This particular
Child Form is never Shown, it only exists for you make copies of it with the New
keyword. These newly created instances of the original Child will be totally
independent and you can manipulate them as separate entities.

Assume the Child's Name is set to frmDocument. Add this code to the general
declarations section of a standard module associated with your project.

'Dynamic array of documents (Forms)


Public Documents() As frmDocument
'Number of created documents
Public DocCount As Integer
Create a File/New menu and add this code:
Private Sub mnuNew_Click()

107
DocCount% = DocCount% + 1
ReDim Preserve Documents(DocCount%) As frmDocument
Set Documents(DocCount%) = New frmDocument
Documents(DocCount%).Caption = "Document " & DocCount%
Documents(DocCount%).Tag = DocCount%
Documents(DocCount%).Show
MDIForm1.Arrange vbCascade

End Sub

Here, we store the document number in the Tag property in order to identify it later.
If the user unloads one of these documents, you will reclaim the memory associated
with it in frmDocument's Unload event:

Private Sub Form_Unload(Cancel As Integer)

Set Documents(CInt(Tag)) = Nothing

End Sub

Page 34: Common Dialog Control


View Page Comments Leave a Comment

The Common Dialog Control is a custom control (.OCX) and must be added to your
toolbox through the Project/Components menu.

This control allows you to access the standard Windows dialogs which every
computer user is familiar with. These are the Open, Save As, Print, Color and Font
dialogs. The control can also display your application's help file, by invoking the
Windows Help engine, but that won't be covered here.

Once the control is drawn on a Form, it appears only as an icon, and will not be
visible at run-time. To use it, you will set the appropriate properties and then run
one the 5 Show methods. The common dialogs are modal forms; code will halt until
the user responds to the dialog by selecting their options and clicking OK (Font,
Color, Printer), Open, Save or Cancel.

There's no need to have more than one Common Dialog control in your project. You
can keep the default name of CommonDialog1, or better yet shorten it to cdl.

Individual properties of the control are intended solely for use with one or two
particular dialogs. The exception to this is the CancelError property, which
generates an error if the user clicks the Cancel button. This would allow you to
branch to error handling code to respond to this situation. Another exception might
be the Flags property, but still, this property has dozens of settings, each used in
one particular style of dialog.

Open, SaveAs dialogs

108
These two dialogs appear identically except for the caption and are shown like this:

cdl.ShowOpen
cdl.ShowSave

The DialogTitle property can be set beforehand. This is virtually the same as setting
the Caption property of a Form.

The Filter property allows you to display only files of certain extensions. This is
somewhat similar to setting the Pattern property of a File List Box control. The
difference is that each filter must have two parts- first, the text which will appear in
the "Save as type:" drop down list and second, the actual filter (or multiple filters
separated by semi-colons). Also, a "pipe" symbol must separate each part, except
for the beginning and end of the Filter string.

cdl.DialogTitle = "Load Picture"


cdl.Filter = "Bitmaps (*.bmp)|*.bmp|Jpegs (*.jpg)|*.jpg|Gifs (*.gif)|*.gif"
cdl.ShowOpen

'Optionally, the Filter string could look like this


cdl.Filter = "Picture Files (*.bmp;*.jpg;*.gif)|*.bmp;*.gif;*.jpg"

By default, the first item in the Filter list, Bitmaps (*.bmp) in this case, will be
initially displayed in the "Save as type:" box. This can be changed by setting the
FilterIndex property. Valid values in this case would be 1, 2, or 3. (0 is the default)

cdl.DialogTitle = "Load Picture"


'Display Jpegs as the default
cdl.FilterIndex = 2
cdl.Filter = "Bitmaps (*.bmp)|*.bmp|Jpegs (*.jpg)|*.jpg|Gifs (*.gif)|*.gif"
cdl.ShowOpen

The drop down list remains the same regardless of the FilterIndex. You might
compare this to setting the ListIndex of a drop down ComboBox.

109
Some users will double click files from the list, others will click files and then the
Open or Save buttons and yet others will type directly into the "File name:" text box.
Many times, a user will type the name of the file and forget about the extension. You
can account for this by setting a value to the DefaultExt property.

cdl.DefaultExt = ".txt"
cdl.Filter = "Text (*.txt)|*.txt|Word document (*.doc)|*.doc|" & _
Rich Text (*.rtf)|*.rtf"
cdl.ShowSave

By default, the Open and Save As dialogs will display files from the current directory,
but you may want to direct the user to where the appropriate files may exist. This is
done with the InitDir property.

cdl.DialogTitle = "Load Picture"


cdl.InitDir = App.Path & "\Images"
cdl.Filter = "Bitmaps (*.bmp)|*.bmp|Jpegs (*.jpg)|*.jpg|Gifs (*.gif)|*.gif"
cdl.ShowOpen
Filename and FileTitle properties

This is the main information you're looking for. Once you have the Filename (full
path) that the user selected, you'll code what needs to be done with it. One common
misconception about the Common Dialog control is that it will actually open or save
the files for you. This is not at all true, the control merely provides a familiar looking
interface for the user.

cdl.DialogTitle = "Load Picture"


cdl.InitDir = App.Path & "\Images"
cdl.Filter = "Bitmaps (*.bmp)|*.bmp|Jpegs (*.jpg)|*.jpg|Gifs (*.gif)|*.gif"
cdl.ShowOpen 'Code stops running until user unloads the dialog
picUser.Picture = LoadPicture(cdl.Filename)

The FileTitle property holds the name of the file and the extension (without the drive
and directories). In this case, that might be useful to label the picture which was
loaded into the Picture Box.

lblUserPic.Caption = cdl.FileTitle
Flags property for Open and SaveAs dialogs

There are many settings for this property, some are set before showing the dialog,
and some are inspected afterwards. Here are some of the useful ones.

Allowing multiple selection of files

Set the Flags property to cdlOFNAllowMultiSelect. The user will be able to hold the
Control key and select multiple files. In this case, the Filename property will hold the
names of all files selected separated by spaces. You'll have to parse this string to get
the individual file names. This will cause a problem with long file names with
embedded spaces. To avoid this, use the cdlOFNNoLongNames setting as well.
Combine multiple settings of the Flags property with the Or operator.

cdl.Flags = cdlOFNAllowMultiSelect or cdlOFNNoLongNames

110
If you must allow multi-selection and long file names, be aware that the individual
file names will be separated by null characters, causing you to use a different parsing
method.

MaxFileSize property

When allowing multi-selection of files, you'll probably need to increase the size of
this property. By default, it is set to 256, the maximum number of characters that
one long file name can have. Since all files selected are stored in one string, 256
might not be enough. You can set this property as high as 32,768.

You can remove the "Open as read-only" Check Box by setting cdlOFNHideReadOnly,
thus not allowing the user to open or save files with the read only attribute.

You might already know how to see if a file exists with the Dir function. Setting the
cdlOFNFileMustExist flag builds this capability right into the dialog. The user will be
given a "This file cannot be found" warning by the system when attempting to open
or save a non-existent file.

When a file does exist, you can put an automatic "This file already exists. Replace
existing file?" prompt into the Save As dialog. The flag setting for this is
cdlOFNOverwritePrompt. This is a Yes/No prompt. There is no way to return a value
(vbYes, vbNo) from this prompt like you would normally do with the MsgBox. If the
user chooses Yes, the dialog will unload and you will respond as usual. If No is
chosen, the dialog will not unload.

Font dialog

Special note about the Font Dialog: the Flags property must be set before showing
the dialog or a "No Fonts Exist" error will occur.

111
If 16 colors is enough for your situation, you can use the Font dialog as a
combination Font and Color dialog. See the Flags property below.

The Font dialog should be used in three steps:

1. Decide which fonts to display to the user by setting the Flags property
2. Show the Dialog with the ShowFont method
3. Apply the selected font, style, and color with the FontName, FontSize,
FontBold, FontItalic, FontStrikthru, FontUnderline, Color properties

At least one of these flags must be set:

• cdlCFScreenFonts (Screen fonts only)


• cdlCFPrinterFonts (Printer fonts only)
• cdlCFBoth (Screen and Printer fonts)

Here are more useful flags:

• cdlCFANSI Only (no "symbol" fonts)


• cdlCFApply (adds an Apply button)
• cdlCFEffects (adds the Effects Frame containing Strikeout and Underline
Check Boxes and drop-down color Combo Box with 16 color choices. These
are the same colors returned by the QBColor function.)
• cdlTTOnly (True Type fonts only)
• cdlCFScalableOnly, cdlCFWYSIWYG (these two are always used together and
with cdlCFBoth. Displays only fonts available to both the screen and the
printer, in other words, "What You See Is What You Get")
• cdlCFForceFontExist (won't let the user type invalid font names into the
"Font:" Text Box. A message will appear saying "There is no font with that
name. Click a font from the list of fonts.")
• cdlCFFixedPitchOnly (display only fixed width fonts. Each character in a fixed
width font is the same width. These types of fonts are useful in many
circumstances.)
• cdlCFLimitSize (see Min and Max properties below)

Min and Max properties

Use these properties to set the range of font sizes which can be selected. You must
set the cdlCFLimitSize flag first. Setting the Min property to 8 or above will also stop
the user from selecting Small Font sizes (2 to 7), but if you want to allow just some
of the small fonts, you can set Min anywhere from 3 to 7 as well. Max will take a
value up to 72.

Here's an example of letting the user decide the font, style, and color of a TextBox.

On Error Resume Next

Dim fnt As StdFont


Set fnt = New StdFont

cdl.Flags = cdlCFBoth Or cdlCFEffects Or cdlCFForceFontExist _

112
Or cdlCFLimitSize Or cdlCFANSIOnly Or _
cdlCFScalableOnly Or cdlCFWYSIWYG
cdl.CancelError = True
cdl.Min = 8: cdl.Max = 24
cdl.ShowFont

With fnt
.Bold = cdl.FontBold
.Italic = cdl.FontItalic
.Name = cdl.FontName
.Size = cdl.FontSize
.Strikethrough = cdl.FontStrikethru
.Underline = cdl.FontUnderline
End With

Set Text1.Font = fnt

Notice we used the StdFont object to encapsulate all the information.

You might also notice that no one can get their act together about the spelling of the
Strikethrough style. On the Font dialog, the spelling is "Strikeout", the Common
Dialog property is spelled "Strikethru", and the StdFont object uses "Strikethrough".

Color dialog

Use the Color dialog if you want to give the user the full range of colors available, all
16,000,000+ of them. The Color dialog comes in two sections, one with 48 Basic
Colors and one where the user can define custom colors.

Flags for the Color dialog

• Set no flags to display the basic colors section only. A Define Custom Colors
button will be available to show that section.

113
• Set the cdlCCFullOpen flag to display both sections
• Set the cdlPreventFullOpen flag to show basic colors and disable the Define
Custom Colors button

After the dialog is unloaded, get the selected color from the Common Dialog's Color
property

Setting a default color

To set a default color, the cdlCCRGBInit flag must be set. Then assign the default
color to the Common Dialog's Color property before showing the dialog.

'Let the user set the Backcolor of the Form


cdl.Flags = cdlCCRGBInit
'Standard gray color
cdl.Color = RGB(192, 192, 192)
cdl.ShowColor
Me.BackColor = cdl.Color
Printer dialog

Coming soon

Page 35: Files and the File System


View Page Comments Leave a Comment

This page will show you how to create, delete, copy, move, and rename files; create,
remove, and rename directories; change the current drive or directory; get or set file
attributes; and use the file system controls.

Creating files

There is no specific VB statement or function which creates an empty file. If you


need to create a new plain text file, use the Open statement. Other types of files
require different processes for creation. For instance, to create an .MDB database,
you'd need to reference the DAO object library in your project and use the
CreateDatabase statement.

Deleting files

Use the Kill statement with the full path of the file to delete contained in a variable,
enclosed in quotes or a combination of the two. You can use wildcard characters to
delete several files all at once.

Wildcard characters

The asterisk (*) is used to represent all characters, while the question mark is used
to represent any one character.

114
This example uses the GetTempPath API function to obtain the user's Temp
directory. The Kill statement then deletes all files in the directory.

Dim nChars As Long


Dim sBuffer As String
Dim sTempPath As String
sBuffer$ = String$(255, 0)
nChars = GetTempPath(Len(sBuffer$), sBuffer$)
sTempPath$ = Left$(sBuffer$, nChars)
Kill sTempPath$ & "*.*"

Kill will not delete an open file. This code would cause an error if any of the files in
the Temp directory were open, so you might want to build some error handling into
your Kill operations.

Also, if a file is successfully deleted, it does not go to the recycle bin, it is


permanently deleted.

I'm sure you can imagine how dangerous the Kill statement could be in the hands of
a derelict programmer.

Here's another example which uses both the * and ? wildcard characters to delete
files with any extension from your application's path where the filename is four
characters long.

Kill App.Path & "\????.*"

There are no "Are you sure you want to delete this file?" prompts included here, like
Windows automatically does. You'll have to build your own messages into your file
operations.

Moving and Renaming files

Both of these actions can be done with the Name statement which is always used
with an As keyword. Wildcard characters are not valid with the Name statement.

This first example lets the user rename a newly created database.

Dim newDB As String


newDB$ = InputBox$("Please specify a filename for the new database")
Name App.Path & "\new.mdb" As App.Path & "\" & newDB$ & ".mdb"
Possible errors with the Name statement

You can't move or rename a file which doesn't exist or is open. The Name statement
will not overwrite any files, so the new name or location cannot already exist. Use
FileCopy to overwrite existing files. Both the old name/location and the new
name/location must be on the same drive. You may have noticed that even Windows
will not move a file between drives; when you attempt this with Windows Explorer or
My Computer, the file is copied, rather than moved. Again, use FileCopy instead.

This second example moves a file from your application's path to a sub-directory of
your application's path without renaming it.
Name App.Path & "\file22.txt" As App.Path & "\txtfiles\file22.txt"

115
You can move and rename a file at the same time. This third example moves the file
to the sub-directory and names it according to the current date.

Name App.Path & "\file22.txt" As App.Path & "\txtfiles\" & Date & ".txt"

You can also rename directories.

Dim NewDirName As String


NewDirName$ = InputBox$("Enter new name for text file directory.")
Name App.Path & "\txtfiles" As App.Path & "\" & NewDirName$

You cannot move entire directories with the Name statement.

Copying files

Use the FileCopy statement with the path of the source file followed by the path of
the target file. Wildcard characters are not allowed.

FileCopy "c:\windows\win.ini", "a:\win.ini"

This example copies win.ini to a floppy disk and would work on my computer but
certainly wouldn't work universally because the paths are hard-coded, not a good
practice at all because you can never assume what the drive letters are or what the
directory structure is. To give your users an easy way to select drives, directories,
and files which exist on their computer, learn to use the file system controls.

File system controls

The file system controls are three special ListBox controls included with the default
toolbox. They can be used separately or in unison. Setting up the controls to give
your user a simple "file system explorer" can be done with just two lines of code.

Draw a DriveListBox, DirListBox, and a FileListBox and add this code:

Private Sub Dir1_Change()


File1.Path = Dir1.Path
End Sub

Private Sub Drive1_Change()

116
Dir1.Path = Drive1.Drive
End Sub

Now the user can select drives, and double-click on directories from that drive to
display all the files in that directory.

Finding out what file is selected

You can construct the path the user has selected from the Path property of the
DirListBox and the filename property of the FileListBox.

Dir1.Path has the same quirk as App.Path, where the property returns a trailing
backslash if the directory is a root of the drive. Use this function to build the path of
the file selected by the user.

Private Function GetSelectedFile(ByVal sPath As String) As String


If Right$(sPath$, 1) <> "\" Then
GetSelectedFile$ = sPath$ & "\" & File1.filename
Else
GetSelectedFile$ = sPath$ & File1.filename
End If
End Function

Call the function like this to avoid the scenario where no file is selected.

If File1.ListIndex = -1 Then
MsgBox "No file selected"
Else
MsgBox GetSelectedFile(Dir1.Path)
End If

Many of the properties which you're already familiar with from the regular ListBox
control apply to the FileListBox. List, ListCount, ListIndex, MultiSelect, Selected, and
TopIndex properties all work the same way, but there are no multiple columns, or
NewIndex and ItemData properties. You cannot add items to the list. The FileListBox
gets it's list from the system, in other words, a read-only list.

The DirListBox is a different story when it comes to the ListIndex property. The
currently open directory takes on a ListIndex of -1. It's parent directory (if any) has
a ListIndex of -2 and it's sub-directories are numbered beginning with 0. This is more
understood by looking at the diagram below.

117
Displaying only certain types of files

Set a value to the FileListBox's Pattern property to filter out only the files which you
think the user will be interested in. You do this with the * and ? wildcards either in
the Properties Window or in code. Multiple patterns can be used by separating them
with semi-colons.

'Display only Rich Text Files and Word Documents


File1.Pattern = "*.rtf;*.doc"

'Display executable files beginning with the letter B


File1.Pattern = "b*.exe"

The FileListBox has 5 more Boolean properties which you can also use to filter out
files to display. This has to do with the properties of the file itself, not so much the
type of file. These 5 file attributes (and properties of the FileListBox) are
ReadOnly, Archive, Normal, System, and Hidden.

By default, the FileListBox will display files with Normal, Archive, and ReadOnly
attributes. In other words, the properties are initially set to True. System and Hidden
properties are initially set to False and files with these attributes will not appear in
the FileListBox unless of course you set those properties to True.

Setting True or False values to these properties will not actually change file
attributes.

Change attributes of a file with the SetAttr statement. A constant is available for
each of the attributes.

'Make AT1 a read-only file.


SetAttr App.Path & "\AT1.txt", vbReadOnly
Also, - vbHidden, vbArchive, vbSystem, vbNormal

Find out a file's attributes with the GetAttr function. This function can also tell if
you've passed it the path to a directory. The function only returns one value which
you need to perform a bitwise comparison on to determine the attributes. This
involves the And operator.

'Is this a hidden file?


If GetAttr(App.Path & "\AT1.txt") And vbHidden Then
MsgBox "File has the hidden attribute"

118
End If

'Is this a directory?


If GetAttr(App.Path) And vbDirectory Then
MsgBox "This is a directory"
End If

Files are also "stamped" with the date and time that they were last modified (or
created). Using the FileDateTime function will return a string containing that date
and time. The result can be used in an expression like the one below which tells me
how many days it's been since my Windows calculator was created.

MsgBox DateDiff("d", FileDateTime("c:\windows\calc.exe"), Now)

Get the size of a file in bytes with the FileLen function.

Dim kilobytes as Long


kilobytes = Format(FileLen("c:\windows\calc.exe") / 1024, "###.00")
MsgBox "The size of the calculator program is " & kilobytes & "KB"

When the user uses the file system controls to navigate around file system, this is a
way to choose files, but does not actually change the drive or directory at the
operating system level. If necessary, you can accomplish that with the ChDir and
ChDrive statements.

'Switch to the user-selected drive


Private Sub Drive1_Change()
Dir1.Path = Drive1.Drive
ChDrive Drive1.Drive
End Sub

'Switch to the A: drive


ChDrive "A"

'Switch to the user-selected directory


Private Sub Dir1_Change()
File1.Path = Dir1.Path
ChDir Dir1.Path
End Sub

'Switch to the Windows directory


ChDir "c:\windows"

Using the GetWindowsDirectory API function would be more valuable in the above
example.

Get the system's current directory with the CurDir function. Each drive has a current
directory, so specify the drive letter if you want the current directory of a drive which
isn't current.

MsgBox "Current directory on the current drive is " & CurDir

MsgBox "Current directory on the floppy drive is " & CurDir("A")

CurDir can often return the same directory as App.Path, but don't use it in place of it.
I've seen that mistake all too often.

You can create new directories with the MkDir statement.

119
'Make a new sub-directory of Windows
MkDir "c:\windows\pictures"

Remove empty directories with the RmDir statement. If the directory contains files,
use the Kill statement first and then RmDir.

Kill "c:\windows\pictures\*.*"
RmDir "c:\windows\pictures"
Finding files

The Dir function is very useful for checking to see if a file exists before performing
operations on it, or for conducting full-blown hard-drive searches. You pass the full
path to a file to the function and if it exists, it returns the name of the file.

If Dir$("c:\windows\win.ini") = "win.ini" Then


MsgBox "File exists"
Else
MsgBox "File does not exist"
End If

Page 36: RichTextBox Control


View Page Comments Leave a Comment

The RichTextBox control is a custom control (OCX) which must be added to your
toolbox through the Project/Components menu.

Besides having all the functionality of a standard TextBox, (without the 64K limit),
the control can display Rich Text (.RTF) files. Rich Text files are capable of text and
paragraph formatting and can also contain embedded objects such as spreadsheets,
images, and .WAV and .MID sounds.

You can create your own .RTF files with Microsoft Word, or even Word Pad. You can
also allow your users to create .RTF documents, format them, and insert objects
from their computer as well. Between Visual Basic and the RichTextBox, you have all
the tools needed to create a full-featured text editor.

LoadFile method

Load either an .RTF or a .TXT file into the control with this method. The first
argument is a string containing the full path to the file, and the second argument is
the type of file.

'rtfRTF is the default, it can be omitted


RichTextBox1.LoadFile App.Path & "\Hwy15.rtf", rtfRTF
RichTextBox1.LoadFile App.Path & "\AB234.txt", rtfText
Filename property

You can also load a file by setting this property to the path of the file.

RichTextBox1.Filename = App.Path & "\Hwy15.rtf"


SaveFile method

120
The SaveFile method uses the same syntax as the LoadFile method; supply the path
to the file and the file type.

'rtfRTF is the default, it can be omitted


RichTextBox1.SaveFile App.Path & "\Hwy15.rtf", rtfRTF
RichTextBox1.SaveFile App.Path & "\AB234.txt", rtfText

Note: If you're allowing your users to open, edit, and save files, you'll probably want
to use some kind of dialog to get the file name and type. You can create your own or
use the Common Dialog control.

Allowing the user format text

If you want to provide this functionality to the user, you should create a toolbar. VB
comes with 16x16 bitmaps for Bold, Italics, Underline, and Strikethru which can be
put on toolbar "on/off" buttons.

Available fonts and font sizes can be added to Combo Boxes on the toolbar. Another
toolbar button can show a Color Dialog for setting the text color.

Text must be highlighted (selected) by the user before formatting can be applied.

Then, simply toggle the appropriate Sel property...

'In the Click event of the toolbar buttons


RichTextBox1.SelBold = Not RichTextBox1.SelBold
RichTextBox1.SelItalic = Not RichTextBox1.SelItalic
RichTextBox1.SelUnderline = Not RichTextBox1.SelUnderline
RichTextBox1.SelStrikthru = Not RichTextBox1.SelStrikethru
...or apply the font or font size...
'In the Click event of the Combo Box
RichTextBox1.SelFontName = cboFonts.Text
RichTextBox1.SelFontSize = CInt(cboFontSize.Text)
...or set the color.
'After showing the Color Dialog
RichTextBox1.SelColor = CommonDialog1.Color
SelChange event

When the user changes the selected text or the insertion point, you'll need to update
the buttons on the toolbar. You can test the value of the Sel properties in this event.
If a particular style of formatting exists throughout the selected text, the Sel
property will return True; if none of the formatting exists, the value will be False; a
mix of formatting will cause a value of Null.

Select Case RichTextBox1.SelBold


Case Null, False: BoldButton.Value = vbUnchecked
Case True: BoldButton.Value = vbChecked
End Select

Write similar Select Case blocks for SelItalic, SelStrikeThru, and SelUnderline.

SelFontName and SelFontSize will return Null for a mix of font or sizes, or the actual
value otherwise.

If Not IsNull(RichTextBox1.SelFontName) Then

121
Combo1.Text = RichTextBox1.SelFontName
End If

If Not IsNull(RichTextBox1.SelFontSize) Then


Combo2.Text = RichTextBox1.SelFontSize
End If
HideSelection property

You might want to set this property to False, otherwise the user will not be able to
see their selected text as they are clicking toolbar buttons (in other words, when the
RichTextBox loses focus).

SelAlignment property

Continuing on with your toolbar, you'll want to create three graphical Option Buttons
(instead of Check Boxes) for left, right, and center aligning text. VB supplies 16x16
bitmaps for this as well. Create a control array named optAlignment and add this
code:

Private Sub optAlignment_Click(Index As Integer)

RichTextBox1.SelAlignment = Index

End Sub
In the SelChange event add this code:
If Not IsNull(RichTextBox1.SelAlignment) Then
optAlignment(RichTextBox1.SelAlignment).Value = True
End If

Since the Index numbers in the control array match the actual values of the
SelAlignment constants (rtfLeft = 0, rtfRight = 1, rtfCenter = 2), we can do these
alignment tasks without a Select Case block.

SelBullet and BulletIndent properties

Add another button to your toolbar to allow insertion of bullets:

'In the toolbar button's Click event


RichTextBox1.SelBullet = Not RichTextBox1.SelBullet

'In the RichTextBox's SelChange event


Select Case RichTextBox1.SelBold
Case Null, False: BulletButton.Value = vbUnchecked
Case True: ButtonButton.Value = vbChecked
End Select

The BulletIndent property can be set at design time, or you can allow the user to set
it. The BulletIndent is the distance between the bullet and the first character of text
on that line. The value of this property is measured by the ScaleMode of the Form.

Superscripting and Subscripting

The SelCharOffset property can do this for you. Set this property to a negative
number of twips (twips always, this is not governed by the ScaleMode of the Form)
to subscript text, and a positive number to superscript. Here's an example which
subscripts the selected text based on the SelFontSize. If SelFontSize returns Null
(mixed font sizes), we don't perform the action.

122
If Not IsNull(RichTextBox1.SelFontSize) Then
RichTextBox1.SelCharOffset = RichTextBox1.SelFontSize * -5
Else
MsgBox "Cannot subscript selection"
End If
Margins

Left margins are set with the SelIndent property and right margins are set with the
SelRightIndent property. The values of these properties are measured by the
ScaleMode of the Form.

Assuming the ScaleMode of the Form is twips and since there are 1440 twips to an
inch, we could use this code to "select all" and set one inch margins.

With RichTextBox1
.SelStart = 1
.SelLength = Len(RichTextBox1.Text)
.SelIndent = 1440
.SelRightIndent = 1440
End With
Locked and SelProtected properties

You can allow the user to protect all or part of the document. Set the SelProtected
property to True to protect the selected text, and False to unprotect selected text. To
protect the entire document, set the Locked property to True. Locked is available at
design time, SelProtected is not.

Printing

Printing with the RichTextBox is much simpler than using the Printer object directly.
The SelPrint method will print the selected area, or, if no there is no selection, the
entire document will print. You'll have to supply a device context to print on. Forms,
PictureBoxes, and the Printer object will qualify (use the hDC property). When
printing to the Printer object there is one small difference; you must initialize the
printer by sending an empty string.

Printer.Print ""
RichTextBox.SelPrint Printer.hDC
ScrollBars and DisableNoScroll properties

Scroll bars are added to a RichTextBox in the same way as a normal TextBox. The
scroll bars will appear when needed. By setting the DisableNoScroll property to True,
scroll bars will appear in a disbaled (grayed out) state even when not needed.
Needless to say, DisableNoScroll will have no affect when ScrollBars is set to 0 -
rtfNone.

There will be more to come in the near future on the RichTextBox including the
searching capability. (Find method, GetLineFromChar method, Span method, UpTo
method) and the OLEObjects collection.

Page 37: Introduction to ADO


View Page Comments Leave a Comment

123
ADO stands for ActiveX Data Objects. ADO is the upgrade to both DAO and RDO. You
can use this library of objects in conjunction with, or in place of the ADO Data
Control for better database manipulation. ADO is much more in tune with database
access on the internet.

To use the objects you must make a reference to them in your project. Go to
Project/References and check the highest version of Microsoft ActiveX Data Objects.
This may be anywhere from 2.0 to 2.6.

Setting up a Connection object is your first task. Create your connection object like
this:

Dim oCn As ADODB.Connection


Set oCn = New ADODB.Connection

Next you must build a connection string and use it as an argument for the Open
method. At a minimum, you'll specify a Provider string which indicates the type of
the database, and a Data Source string which is simply the file name. Individual
setting=value combinations of the connection string are separated with semi-colons.

Dim sConnect As String


sConnect = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=c:\database\myaccessfile.mdb"
oCn.Open sConnect

The above value for Provider refers to an Access 2000 file (Jet 4.0). For Access 97
use 'Microsoft.Jet.OLEDB.3.51' (Jet 3.51).

All of the above replaces the OpenDatabase method of the Workspace object in DAO.

Once your connection is established, you can get your recordsets by creating an
'ADODB.Recordset' object and then using it's Open method. This method will accept
the name of a table or query, or an SQL string. You'll need to add the name of the
connection object and three more parameters - the type of recordset to open
(CursorType), the type of record locking (LockType), and type of command
(CommandType). The type of command refers to whether you are fetching a table
(adCmdTable), a query (adCmdStoredProc), or using an SQL string (adCmdText).

Dim oCn As ADODB.Connection


Dim oRs As ADODB.Recordset
Set oCn = New ADODB.Connection
Set oRs = New ADODB.Recordset
oCn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=c:\database\myaccessfile.mdb"
oRs.Open "users", oCn, adOpenKeyset, adLockOptimistic, adCmdTable
Other options for the CursorType are adOpenDynamic, adOpenForwardOnly, and
adOpenStatic. The static cursor is read only, similar to the Snapshot type from DAO.
Forward Only recordsets only allow a single loop through the records. (MovePrevious
would not be allowed.) The Keyset is a new term with ADO.

Keyset and Dynamic are similar recordsets which allows all types of movement and
changes to the data, but the Keyset is preferred in a multi-user database where
changes by other users are not needed immediately. The Dynamic recordset will
show changes by other users immediately (automatic refreshing), but will of course
take a chunk out of performance.

124
If multiple users try to update the same record at the same time, an error will occur.
When locking your records, you will either be optimistic that this error won't occur,
or you will be pessimistic and figure this error is likely to happen. In optimistic
locking, other users are only locked out of the record(s) when the Update method is
called - probably just a split second. In pessimistic locking, other users are locked
out for the entire period that the records are being accessed and modified.

The ADODB.Recordset's Open method replaces the OpenRecordset method from


DAO's Database object.

The examples below will assume that module level Recordset and Connection object
(oRs & oCn) have been property created in Form_Load and buttons named
cmdAddNew, cmdFindUser, cmdUpdate, and cmdDelete.

The users table from the code above has an AutoNumber ID field, and Name and
EMail text fields. As long as your recordset is not a read-only type, you can add a
new record like this:

Private Sub cmdAddNew_Click()

With oRs
.AddNew
.Fields("Name") = txtName.Text
.Fields("EMail") = txtEMail.Text
.Update
End With

End Sub
You wouldn't assign a value to the ID field because the next sequential number
would be automatically inserted by the database.

Some programmers know that the Fields collection is the default collection for the
ADODB.Recordset and the Text property is the default property for a TextBox and
might shorten this same code to:

oRs.AddNew
oRs("Name") = txtName
oRs("EMail) = txtEMail
oRs.Update
To update or delete a particular record, you would first need to make the record
current. You could do this two ways - use an SQL string with a WHERE clause when
opening the recordset, or use the Find method with the correct criteria. The criteria
used for the Find method is exactly like the SQL WHERE clause without the WHERE
keyword.
Private Sub cmdFindUser_Click()

Dim uid As Variant

Do Until IsNumeric(uid) And Len(uid) > 0


uid = InputBox("Enter your ID to change your personal information.")
'User did not enter anything or pressed cancel
If uid = "" Then Exit Sub
Loop

'Using an SQL string - comment out if using Find method


oRs.Open "SELECT [Name], [EMail] FROM users WHERE [ID] = " & _
uid & ";", oCn, adOpenKeyset, adLockOptimistic, adCmdText

'Using Find method - comment out if using SQL string method

125
oRs.Open "users", oCn, adOpenKeyset, adLockOptimistic, adCmdTable
oRs.Find "[ID] = " & uid

If oRs.RecordCount > 0 Then


txtName = oRs("Name")
txtEMail = oRs("EMail")
End If

'At this point, the user edits the TextBoxes and


clicks the update button or clicks the Delete button

End Sub

Private Sub cmdUpdate_Click()

oRs("Name") = txtName
oRs("EMail") = txtEMail
oRs.Update

End Sub

Private Sub cmdDelete_Click()

oRs.Delete
txtName = ""
txtEMail = ""

End Sub

Now, a variety of errors may occur if you use this code, two of which come to mind.
The Recordset will not be open if the user clicks Cancel or enters nothing and clicks
OK in the InputBox, and the Recordset will have no current record if the ID entered
is not valid. You could throw some On Error Resume Next statements in each of the
procedures, but that may leave the user confused. So for the most part I'll the leave
that as an exercise in error trapping for the VB student.

Make sure to close and destroy your ADO objects when they're no longer needed. If
they are module level, do this in Form_Unload. If they are procedure level, do this
right in the procedure.
oRs.Close
oCn.Close
Set oRs = Nothing
set oCn = Nothing

Page 38: Introduction to SQL


View Page Comments Leave a Comment

SQL stands for Structured Query Language and it is usually pronounced sequel. SQL
is the standard language of relational database management systems (RDBMS) as
decided by the American National Standards Institute (ANSI). Microsoft SQL Server,
Microsoft Access, Oracle, and Sybase all use SQL as their engine.

In VB, SQL is used by constructing strings, and applying them to methods or


properties to return recordsets or perform actions on the database such as adding,
deleting, and updating fields, records, and tables. An SQL string can be applied to:

• The RecordSource property of the Data control


• The first argument of the Execute method of the Database object

126
• The first argument of the OpenRecordset method of the Database object

The Data control creates the same Database and Recordset objects as DAO, so don't
feel you must make a reference to DAO unless you need database access beyond
those two objects.

SELECT queries

A SELECT query is a database search. You'll supply what fields to return from what
tables and also the criteria to search for.

The basic syntax for a select query is:

"SELECT fields FROM tables WHERE criteria;"

fields is a comma-delimited list of fields to return, tables is a comma-delimited list of


where to find the fields, and criteria is an expression that records must satisfy in
order to be part of the resulting recordset.

Data1.RecordSource = "SELECT City, Population FROM " & _


"Census1990 WHERE State = 'CA';"

This SQL string says- Create a recordset with only City and Population fields from the
Census1990 table. Return only the records whose State field contains the value 'CA'.
When the value to check for in the WHERE clause is string type, it must be enclosed
within single quotes. The quotes aren't used with numeric values.

The above SQL string deals with one table only. When searching multiple tables, you
need to supply the table where each field resides.

Data1.RecordSource = "SELECT Census1980.City, Census1990.Population " & _


"FROM Census1980, Census1990 WHERE Census1990.State = 'CA';"

SQL strings are not case-sensitive, but you'll commonly see programmers using
upper-case for SQL keywords. Syntax must be perfect! Spaces and commas must be
in the correct places. Notice a space between each member of the string. Also notice
the string is terminated with a semi-colon.

If a field name has embedded spaces, you'll need to enclose it in brackets.

Data1.RecordSource = "SELECT [City Founder], Population FROM Census1990 " & _


"WHERE Population BETWEEN 10000 AND 50000;"

Take a look at the WHERE clause. We've used the BETWEEN lowerbound AND
upperbound operator to return records whose Population field falls within a range.

If you want every field from the table included in the recordset, you can either use
the keyword ALL or the asterisk (*) wildcard in place of field names.

Data1.RecordSource = "SELECT * FROM Census1990 " & _


"WHERE State LIKE 'A*' ORDER BY State ASC;"

127
Here, we're getting all fields from the Census1990 table and returning just the
records whose State field begins with the letter A. Don't forget to enclose patterns
used with the LIKE operator between single quotes. Also, an ORDER BY clause has
been added to the end. This will sort the recordset alphabetically. The ASC keyword
means "ascending" (A-Z), but that is the default sort order, so it can be omitted. To
sort (Z-A), you would change ASC to DESC. If the field name after ORDER BY is a
numeric field, sorting will take place in the (0-9) order for ASC and (9-0) for DESC.

This is just a basic introduction to the SELECT query. Your VB help file includes SQL
documentation. In particular, see AS, GROUP BY, HAVING, TOP, DISTINCT, and
DISTINCTROW.

Action queries

An action query doesn't return a recordset, but rather performs an operation on the
database. Updating records, deleting records, inserting new records, removing
tables, and creating new tables are some of the action queries.

For updating records the syntax is:

"UPDATE table SET field = newvalue WHERE criteria;"

Data1.Database.Execute "UPDATE Census2000 " & _


"SET Population = 170321 WHERE City = 'Chula Vista';"

Note we're now using the Execute method of the Database object (an object which is
a property of the Data control). This line of code has changed the Population field in
the Chula Vista record to a new value.

To delete records, the syntax is:

"DELETE FROM tables WHERE criteria;"


Data1.Database.Execute "DELETE FROM Census1980 WHERE Population < 10000;"

Adding records is a little more involved, you need to list all the fields in the table and
all the new values within the SQL string, remembering to enclose text values in
single quotes. The syntax for an append query is:

"INSERT INTO
table (Field1Name, Field2Name, etc)
VALUES (Field1Value, Field2Value, etc);"

Data1.Database.Execute "INSERT INTO Census2000 " & _


"(City, Population, State) VALUES ('El Cajon', 87213, 'CA');"

The values listed between the parentheses after VALUE must match the list of fields
after the table.

Creating a new table involves listing all the fields the new table will have and also
the data type of the field. The syntax is:

"CREATE TABLE tablename


(Field1Name Field1Type, Field2Name Field2Type, etc.);"

Data1.Database.Execute "CREATE TABLE Census2000 " & _

128
"(City TEXT (32), Population LONG, State TEXT (2));"

Notice the (32) and (2) after the two TEXT fields. This limits the field to a certain
number of characters.

Deleting a table is fairly simple. The syntax is:

"DROP TABLE tablename;"

Data1.Database.Execute "DROP TABLE Census1980;"

Often, you'll want to use VB variables in your SQL strings. When doing this, you'll
concatenate the variables to the rest of the SQL string as you already know how to
do, but remember to enclose these variables in single quotes.

sTable$ = InputBox("Enter name of table to delete")


Data1.Database.Execute "DROP TABLE '" & _
sTable$ & "';"

Page 39: Introduction to API


View Page Comments Leave a Comment
What is Windows API?

API stands for Application Programming Interface. Using API means using pre-
written procedures (mostly functions) contained in .DLL files. DLL stands for Dynamic
Link Library; a library of procedures which you can link to dynamically (at run-time).
In other words, a .DLL is a file full of executable code. These .DLL files were copied
to the user's computer when they installed Windows, so you can be sure that they
are already in place, available for you to use.

Why do I need these procedures?

There are many tasks that are difficult to accomplish with VB, and many more that
are not possible at all. Procedures in .DLL's contain efficient code written in the
powerful 'C' language by programmer's more talented than you or I.

For instance, you can use VB's App.Path to return the directory of your .EXE file, but
try finding the user's Windows directory, or their system or temp directories for that
matter. You can't do it with VB alone, but you can with the help of API.

With API you can do complex drawing, sophisticated bitmap transfers, get system
information and much more. Did you ever wonder how to put those little pictures
into a menu? A little fancy API, that's all. Procedures in .DLL's generally run faster
than VB code as well.

How do I use the .DLL's?

You do not need to add the .DLL's to your project in order to use their procedures
like you might do with a VB .BAS module. You must add a Declare statement to the
general declarations section of a Form or .BAS module for every procedure you want
to use. These Declare statements are pre-written for you in the API Text Viewer

129
which comes with VB. In most cases, you'll copy the statements to a .BAS module in
your project (this way they'll be Public and available to use throughout the project).
If you put Declare statements in the Form, they are forced to be Private and must be
preceded by the Private keyword. You can put them in Class (.CLS) modules too, but
like Forms, they must be Private.

Declare Function GetWindowsDirectory Lib _


"kernel32" Alias "GetWindowsDirectoryA" (ByVal _
lpBuffer As String, ByVal nSize As Long) As Long

This is the Declare statement which is used to return the user's Window's directory.
A Declare statement is the only place you'll find the Lib and Alias keywords. The
literal string following the Lib keyword is the name of the .DLL file which contains the
procedure. Notice that it's not required to use "kernel32.dll". The .DLL extension is
assumed. The Alias keyword is optional. If it's used, it specifies the name of the
procedure as it actually exists in the .DLL. Knowing that, you can give the Function
any name you want as long the string following the Alias keyword points to the
correct name in the .DLL. You could change the above Declare statement to:

Declare Function WinPath Lib _


"kernel32" Alias "GetWindowsDirectoryA" (ByVal _
lpBuffer As String, ByVal nSize As Long) As Long

And you would get the user's Windows directory like this:

Dim nChars As Long


Dim sBuffer As String
Dim sWinPath As String
sBuffer$ = String$(255, 0)
nChars& = WinPath(sBuffer$, Len(sBuffer$))
sWinPath$ = Left$(sBuffer$, nChars&)

This probably looks a little complicated for just trying return a string from a Function.
The GetWindowsDirectory Function requires that you pass it a buffer string to copy
the information onto. The return value of GetWindowsDirectory is actually the
number of characters copied to the buffer. The string you need is then extracted
from the left side of the buffer string. More on buffer strings.

If you're a little confused, that's OK, nobody learns how to use API in a day. API is a
never-ending learning experience. There are thousands of procedures available.

Why are .DLL procedures mostly functions?

Some .DLL procedures return a useful value, such as a handle to a window or an


object, but very often .DLL functions return the simple value of True or False. True
(non-zero) indicates the procedure completed itself successfully, False signifies an
error. Some API functions internally set a value when an error occurs which you can
retrieve with the GetLastError function.

How do get I documentation on these procedures?

Unfortunately, the API Text Viewer doesn't give you any information about what the
return value is used for, specifics about the arguments, or even what it actually
does. My best recommendation is to buy Dan Appleman's Visual Basic 5.0

130
Programmer's Guide to the Win32 API. This will set you back about $60.00, but
it's worth every penny. The book comes with a CD-ROM help file and numerous
sample projects. If you're short of money, try finding a VB message board online and
ask questions. Many people are willing to help.

Page 40: Accessing INI Files


View Page Comments Leave a Comment
What is an INI file?

An .INI file is a plain text file set up to store and retrieve settings. These could be
the user's personal settings for running your program, or small pieces of data which
need to be saved externally from the program, such as the number of times or
number of days that your program has been run. .INI files are not for storing large
amounts of data.

INI Files vs. Windows Registry

Since Windows programmers can now use the Registry, INI files might be considered
deprecated in most cases. Many users are intelligent enough to edit an INI file, but
have no knowledge of the structure of the Registry. If you don't want your settings
tinkered with, use the Registry instead.

INI files are formatted with sections, entries, and values as follows.

[Section1]
entry=value
entry=value
entry=value
[Section2]
entry=value
entry=value

In API-speak, a section is referred to as ApplicationName, because different


applications can use the same INI file, such as WIN.INI. An entry is called a
KeyName and a value is called String because the values are stored as strings,
whether character or numeric.

So decide if you want to store your application's settings in WIN.INI or ship your own
custom INI file with your program. I recommend creating your own, it's more
flexible.

There are two API functions which are specially used for accessing your own INI file
and two more for accessing WIN.INI.

"Private" .INI files


'Read settings
Declare Function GetPrivateProfileString Lib "kernel32" Alias _
"GetPrivateProfileStringA" (ByVal lpApplicationName As String, _
ByVal lpKeyName As Any, ByVal lpDefault As String, _
ByVal lpReturnedString As String, ByVal nSize As Long, _
ByVal lpFileName As String) As Long

131
At first glance, this probably looks very complicated. The first thing you need to
realize about GetPrivateProfileString is that you need to supply a buffer string. You
set up this buffer string with null characters. The function copies the setting onto the
left side of the buffer string and the value it returns is the number of characters
copied to the buffer. You then extract the setting from the buffer.

This example retrieves the PROVIDER setting from the ETVWIN section of this
sample .ini file:

*************************************
[Mail]
MAPI=1
MAPIX=1
OLEMessaging=1
CMC=1
SAMPLE INI FILE
[ETVWIN]
PATH=C:\ETVWIN
VERSION=1.54q
PROVIDER=PACKARD BELL
LASTFILE=C:\ETVWIN\SAMPLE.TV
******************************************
'CODE
Dim x As Long
Dim sSection As String, sEntry As String, sDefault As String
Dim sRetBuf As String, iLenBuf As Integer, sFileName As String
Dim sValue As String

'Six arguments
sSection$ = "ETVWIN"
sEntry$ = "PROVIDER"
sDefault$ = ""
sRetBuf$ = String$(256, 0) '256 null characters
iLenBuf% = Len(sRetBuf$)
sFileName$ = "c:\windows\sample.ini"

'function will return a value of 12 in this case


x = GetPrivateProfileString(sSection$, sEntry$, _
sDefault$, sRetBuf$, iLenBuf%, sFileName$)

'sValue$ will contain the string "PACKARD BELL"


sValue$ = Left$(sRetBuf$, x)
Explanation of arguments:

sSection$: ini file section (always between brackets)


sEntry$ : word on left side of "=" sign
sDefault$: value returned if function is unsuccessful
sRetBuf$ : the value you're looking for will be copied to this buffer string
iLenBuf% : Length in characters of the buffer string
sFileName: Path to the ini file

'Write settings
Declare Function WritePrivateProfileString Lib "kernel32" Alias _
"WritePrivateProfileStringA" (ByVal lpApplicationName As String, _
ByVal lpKeyName As Any, ByVal lpString As Any, _
ByVal lpFileName As String) As Long

Writing settings is a little easier. Just supply the section, entry, value, and path to
the INI file.

The code below assigns the value 2 to the MAPI entry in the sample ini file above.

132
Dim x As Long
Dim sSection As String
Dim sEntry As String
Dim sString As String
Dim sFileName As String

sSection$ = "Mail"
sEntry$ = "MAPI"
sString$ = "2"
sFileName$ = "c:\windows\sample.ini"

x = WritePrivateProfileString(sSection$, sEntry$, sString$, sFileName$)

If x Then
MsgBox "Setting has been saved"
Else
MsgBox "Error saving setting"
End If

WritePrivateProfileString returns a True value if successful, otherwise it returns


False.

Bonus: WritePrivateProfileString will create the INI file in the Windows directory if it
doesn't exist.

Reading and writing to WIN.INI is even easier. Use these two similar API functions:

Declare Function GetProfileString Lib "kernel32" Alias _


"GetProfileStringA" (ByVal lpAppName As String, _
ByVal lpKeyName As String, ByVal lpDefault As String, _
ByVal lpReturnedString As String, ByVal nSize As Long) As Long

Declare Function WriteProfileString Lib "kernel32" Alias _


"WriteProfileStringA" (ByVal lpszSection As String, _
ByVal lpszKeyName As String, ByVal lpszString As String) As Long

These two functions are identical to their "private" counterparts except for specifying
the filename. The functions will find WIN.INI on their own.

Page 41: Sound and Music


View Page Comments Leave a Comment

Unfortunately, VB has only one intrinsic statement which will play sound. The Beep
statement requires no parameters and will play the default system sound, usually
that annoying 'ding'.

The Microsoft Multimedia custom control can be used for playing and recording .WAV
sounds and .MID music, playing .AVI movies, and accessing the CD-ROM and other
devices, but the author of this tutorial is somewhat anti-OCX and will not explain this
control. A talented VB programmer will use the multimedia API calls.

You may need to read Page 39: Introduction to API before moving on.

Playing .WAV Sounds

133
Simple playing of a .WAV sound should be done with sndPlaySound function. The
following Declare statement and constants will be used. You supply the full path to
the sound file and the flags, which are a combination of the desired constants.
Multiple constants are added together with the Or operator.

Declare Function sndPlaySound Lib "winmm.dll" _


Alias "sndPlaySoundA" (ByVal lpszSoundName _
As String, ByVal uFlags As Long) As Long

Public Const SND_ASYNC = &H1


Public Const SND_LOOP = &H8
Public Const SND_NODEFAULT = &H2
Public Const SND_NOSTOP = &H10
Public Const SND_SYNC = &H0
First, decide if you want to play the sound synchronously or asynchronously. The
SND_ASYNC constant is used to play the sound without tying up the processor. If
SND_SYNC is used, your program will not resume executing code or process any
events until the sound finishes playing.
Dim retVal As Long
retVal& = sndPlaySound(App.Path & "\mySound.wav", SND_ASYNC)

'or...
retVal& = sndPlaySound(App.Path & "\mySound.wav", SND_SYNC)
If the sound file cannot be found, the default system sound will play. Avoid that by
adding the SND_NODEFAULT constant.
retVal& = sndPlaySound(App.Path & "\mySound.wav", _
SND_ASYNC Or SND_NODEFAULT)
Sounds can be continously looped by adding the SND_LOOP constant. There is no
way to specify the number of times the sound is looped, so you'll need to manually
stop the sound by using vbNullString constant as the file name.
retVal& = sndPlaySound(App.Path & "\mySound.wav", _
SND_ASYNC Or SND_NODEFAULT Or SND_LOOP)

'Stop the sound


retVal& = sndPlaySound(vbNullString, 0)
Adding the SND_NOSTOP constant will prevent the sound from playing if another
WAV is playing asynchronously. If the first WAV is playing synchronously, the second
WAV will still play when the first finishes.
retVal& = sndPlaySound(App.Path & "\mySound.wav", _
SND_ASYNC Or SND_NODEFAULT _
Or SND_LOOP Or SND_NOSTOP)
The return value of the sndPlaySound function will be 1 if the sound played
successfully (even if the default sound plays). If there is some type of error, (or you
use SND_NODEFAULT and the sound can't be found), then the return value will be 0.

Personally, I'm in the habit of discarding the return value by calling the function in
the style of a method.

sndPlaySound App.Path & "\mySound.wav", SND_ASYNC


Playing .MID music

Playing MIDI's is much more involved than playing WAV's. You'll need the all-purpose
mciSendString function.

Private Declare Function mciSendString Lib "winmm.dll" _


Alias "mciSendStringA" (ByVal lpstrCommand _
As String, ByVal lpstrReturnString As String, _
ByVal uReturnLength As Long, ByVal hwndCallback _
As Long) As Long

134
• lpstrCommand - this is the command string which contains instructions on
what action to perform, whether opening, playing, stopping, closing, or
getting the status of a currently playing MIDI.
• lpstrReturnString - a string buffer which you can supply to get status
information about a MIDI, otherwise use 0.
• uReturnLength - The length of lpstrReturnString or 0.
• hwndCallback - The location of a callback function. This might be used to have
the function notify you when the music is done playing (in order to loop the
MIDI), but we will avoid callbacks by continually requesting information with a
Timer control. Therefore, a value of 0 will be used.

In order to retrieve information about any errors which occur, you'll need the
mciGetErrorString function.

Private Declare Function mciGetErrorString Lib _


"winmm.dll" Alias "mciGetErrorStringA" _
(ByVal dwError As Long, ByVal lpstrBuffer _
As String, ByVal uLength As Long) As Long
The file name of the MIDI will be embedded in the command string, so you might
imagine the problem mciSendString would have with long file names which have
embedded spaces, so the first step to playing a MIDI is to convert long file names to
short file names. This is done with the following Declare statement.
Private Declare Function GetShortPathName Lib _
"kernel32" Alias "GetShortPathNameA" (ByVal _
lpszLongPath As String, ByVal lpszShortPath _
As String, ByVal cchBuffer As Long) As Long
The next step is to make sure the device is closed with a "close all" command. This
will stop any currently playing MIDI's and basically clean things up so that you can
send an "open" command to the device. It's a good idea to give your .MID file an
alias in the "open" command. The "open" command is followed by the short file
name enclosed in quotes, the type of device to open and the alias. This shorter alias
can be used in subsequent commands in place of file names. Finally, send the "play"
command. Here is the entire procedure which begins playing a .MID file.
Public Sub PlayMid(ByVal sLongPath As String)

Dim retStr As String * 255


Dim retVal As Long, sShortPath As String

sShortPath$ = Space(Len(sLongPath$))

retVal& = GetShortPathName(sLongPath$, _
sShortPath$, Len(sShortPath$))

If retVal& > Len(sLongPath$) Then


sShortPath$ = sLongPath$
Else
sShortPath$ = Left(sShortPath$, retVal&)
End If

retVal& = mciSendString("close all", 0, 0, 0)

retVal& = mciSendString("open " & Chr(34) & sShortPath & Chr(34) & _
" type sequencer alias mymidi", 0, 0, 0)
If retVal& <> 0 Then GoTo errorhandler

retVal& = mciSendString("play mymidi", 0, 0, 0)


If retVal& <> 0 Then GoTo errorhandler

Exit Sub

errorhandler:
mciGetErrorString retVal&, retStr$, 255

135
MsgBox retStr$

End Sub
To stop the MIDI, you'll need to use the return string parameters and send a "status"
command and check the "mode". If the return string indicates that the MIDI is
"playing", we send a "stop" command. If it is already "stopped", then we "close all".
Public Sub StopMid()

Dim retVal As Long


Dim retStr As String * 255

retStr = Space(255)

retVal& = mciSendString("status mymidi mode", retStr$, 255, 0)


If Left(retStr$, 7) = "playing" Then
retVal& = mciSendString("stop mymidi", 0, 0, 0)
Exit Sub
End If

retVal& = mciSendString("status mymidi mode", retStr$, 255, 0)


If Left(retStr$, 7) = "stopped" Then
retVal& = mciSendString("close all", 0, 0, 0)
End If

End Sub
To loop the MIDI, we can use the following procedure in a Timer control with interval
set to 500 or lower. Again, the "status" of the "mode" is checked. If "stopped", we
restart by calling StopMid() and PlayMid().
Public Sub CheckMid()

Dim retVal As Long


Dim retStr As String * 255

retVal& = mciSendString("status mymidi mode", retStr$, 255, 0)


If retVal& <> 0 Then Exit Sub
If Left(retStr$, 7) = "stopped" Then
StopMid
PlayMid App.Path & "\someMidi.mid"
End If

End Sub

Private Sub Timer1_Timer()


CheckMid()
End Sub
This is only the tip of the iceberg as far as MCI (Media Control Interface) is
concerned. An entire book could be written about the functions and command
strings. Microsoft's web site has a complete MCI Reference

Page 42: Class Modules


View Page Comments Leave a Comment

Object Oriented Programming (OOP) was introduced to make large programs easier
to create, but you may find it a bit confusing at first. The idea of OOP is to
encapsulate programming tasks into individual objects, each with their own
properties, methods, functions, and enumerations. A class module is the blueprint, or
template for your own custom objects. When you need the functionality of a
particular object, you create an instance of it, based on the code contained in the
class module.

136
Recently, I created a ScrollingText class and this page will act as documentation for
the class. The class with sample project is available for download here.

Getting started

If you have the Class Builder Utility Add-In, I suggest using it to add a class module
to your project. The Add-In will create the framework of your class, and throw in a
few helpful comments. Otherwise, add a class the manual way through the
Project/Add Class Module menu. Name the class as the first order of business. This
name is the class module's data type.

The first thing I did with the ScrollingText class was to create a Caption property (the
actual text to scroll).

Creating properties

At it's root, a property is merely a variable which holds the property's value. For each
property you create, you will have a module-level variable, and Property Let and
Property Get procedures. Property procedures are the code which runs when a
property gets a new value (Let) or when the value is retrieved (Get). This is how the
Caption property of the ScrollingText class was set up. You can use the Add
Procedure window to help with this.

'general declarations
Private m_Caption As String

'property procedures
Public Property Let Caption(ByVal NewCaption As String)

m_Caption = NewCaption

End Property

Public Property Get Caption() As String

Caption = m_Caption

End Property
The actual value of the Caption property is held in the m_Caption variable. The
Property Get procedure simply gets the value of m_Caption when a request is made.
This is similar to returning a value from a Function procedure. Property Let
procedures are required to have an argument; m_Caption takes on the value of this
argument.

You might be thinking "Why all the hoopla? Why don't we just use a Public variable
and skip the complicated property procedures?". Well, actually you can do that, but
it's pretty much an accepted practice to use the procedures. You could add code to
property procedures to validate the value being assigned, display messages and so
on. I might improve the Caption Property Let procedure by limiting the text to 100
characters.

Public Property Let Caption(ByVal NewCaption As String)

If Len(NewCaption) > 100 Then


MsgBox "Maximum 100 characters", vbCritical, "Caption property"
m_Caption = "Caption Error": Exit Property
End If

137
m_Caption = NewCaption

End Property
Now let's look at the code which creates an instance of ScrollingText.
Private Sub Form_Load()

'Declare an object variable as type ScrollingText


Dim scrMessage As ScrollingText
'Create a new ScrollingText object
Set scrMessage = New ScrollingText

'This line triggers the Property Let procedure


"Some scrolling text" is the NewCaption argument
scrMessage.Caption = "Some scrolling text"

'Since we are retrieving the value of the property,


this line triggers the Property Get procedure
txtMessage = scrMessage.Caption

End Sub

The following Public properties were also created for the ScrollingText class:

• BeginX and BeginY (initial text coordinates)


• Color (text color)
• ScrollLoop (for continous scrolling)
• Speed (scrolling speed)
• Scrolling (a Boolean start/stop switch)

Also, two Private properties were created:

• CurrentX and CurrentY

Since the values of these two properties are only manipulated within the class, I
made them Private.

A Parent property was created in order to have access to the Form where the
scrolling text was being drawn. Objects such as Forms can also be used as properties
of a class. The difference here is that you'll need a Property Set procedure, rather
than a Property Let.

Private m_Parent As Form

Public Property Get Parent() As Form

Set Parent = m_Parent

End Property

Public Property Set Parent(ByVal NewForm As Form)

Set m_Parent = NewForm

End Property
Enumerations

For some properties, you'll want to limit the value that can be set to a particular list.
An good example of this would be the ScrollBars property of a TextBox. The property
can only be set to a value of 0 to 3 or one of the ScrollBarConstants.

138
I wanted a Direction property for my ScrollingText class to allow the text to scroll
left, right, up, or down. Since this property could only have one of four values, I
decided to create an enumeration. The Enum is created in the general declarations
section of the class. The name of the Enum becomes the data type used with the
property variable and procedures.

Public Enum DirectionConstants


stRight
stLeft
stUp
stDown
End Enum

Private m_Direction As DirectionConstants

Public Property Get Direction() As DirectionConstants

Direction = m_Direction

End Property

Public Property Let Direction(ByVal NewDirection As DirectionConstants)

m_Direction = NewDirection

End Property
By default, the numeric value of these constants are automatically assigned
according to their position in the list. (stRight = 0, stLeft = 1, etc.). Actual values
can also be assigned.
Public Enum DirectionConstants
stRight = 0
stLeft = 1
stUp = 2
stDown = 3
End Enum
Creating methods

Creating methods is very easy compared to Properties. Just use your Add Procedure
window to add a Sub or Function routine. Functions are simply methods which return
a value. For the ScrollingText class, I created a StartScroll method which does most
of the work.

Public Sub StartScroll()

Scrolling = True
Dim stTime As Long
CurrentX = BeginX
CurrentY = BeginY

With Parent

Do
stTime& = GetTickCount()
SetTextColor .hdc, Color
TextOut .hdc, CurrentX, CurrentY, Caption, Len(Caption)
Do Until GetTickCount - stTime > Speed
DoEvents: If Not Scrolling Then Exit Sub
Loop
SetTextColor .hdc, GetBkColor(.hdc)
TextOut .hdc, CurrentX, CurrentY, Caption, Len(Caption)
SetNewPosition
DoEvents: If Not Scrolling Then Exit Sub
Loop

End With

139
End Sub

The following API functions are used. Remember, if Declare statements are used in a
class, they must be Private.

Private Declare Function GetBkColor _


Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function GetTickCount _
Lib "kernel32" () As Long
Private Declare Function TextOut _
Lib "gdi32" Alias "TextOutA" (ByVal _
hdc As Long, ByVal x As Long, ByVal _
y As Long, ByVal lpString As String, _
ByVal nCount As Long) As Long
Private Declare Function SetTextColor _
Lib "gdi32" (ByVal hdc As Long, ByVal _
crColor As Long) As Long

TextOut is a text drawing function similar to VB's Print method. SetTextColor is


required because changing the ForeColor of the Form has no effect when using
TextOut. The GetTickCount function returns the number of milliseconds which have
passed since the current Windows session began, so I use it for delays in between
two calls to TextOut - one to draw the text, a second to erase the text by drawing
text in the same color as the BackColor of the Form. I first tried using the Form's
BackColor as an argument for SetTextColor, but the value of BackColor is actually a
windows handle to the selected brush, so I was forced to use GetBkColor to retreive
the correct color.

To stop the scrolling, I added a StopScroll method.

Public Sub StopScroll(Optional bErase As Boolean)

Scrolling = False
If bErase = True Then
With Parent
SetTextColor .hdc, GetBkColor(.hdc)
TextOut .hdc, CurrentX, CurrentY, Caption, Len(Caption)
End With
End If

End Sub

The bErase argument can be set to True to immediately erase any text after scrolling
has been stopped. I stuck the Optional keyword in front of the argument so that it
can be omitted in a False situation.

The last method I added was SetNewPosition which is only called from StartScroll, so
I made it a Private Sub. This method calculates the next position to draw depending
on the Direction and ScrollLoop properties. Remember, you can simply use the name
of the property by itself when writing code within the class.

Private Sub SetNewPosition()

With Parent

Select Case Direction


Case stRight
CurrentX = CurrentX + 1
If CurrentX > .ScaleWidth And ScrollLoop Then

140
CurrentX = 0: RaiseEvent TextLooped
ElseIf CurrentX > .ScaleWidth And Not ScrollLoop Then
Scrolling = False
End If
Case stLeft
CurrentX = CurrentX - 1
If CurrentX + .TextWidth(Caption) < 0 And ScrollLoop Then
CurrentX = .ScaleWidth: RaiseEvent TextLooped
ElseIf CurrentX + .TextWidth(Caption) < 0 And Not ScrollLoop Then
Scrolling = False
End If
Case stUp
CurrentY = CurrentY - 1
If CurrentY + .TextHeight(Caption) < 0 And ScrollLoop Then
CurrentY = .ScaleHeight: RaiseEvent TextLooped
ElseIf CurrentY + .TextHeight(Caption) < 0 And Not ScrollLoop Then
Scrolling = False
End If
Case stDown
CurrentY = CurrentY + 1
If CurrentY > .ScaleHeight And ScrollLoop Then
CurrentY = 0: RaiseEvent TextLooped
ElseIf CurrentY > .ScaleHeight And Not ScrollLoop Then
Scrolling = False
End If
End Select

End With

End Sub
Creating Events

I added a TextLooped() event to the ScrollingText class with the following statement
in the general declarations section.

Public Event TextLooped()


When text runs off one end of the Form, this events fires just before it's drawn again
on the other end. You have to trigger the event with the RaiseEvent statement.
RaiseEvent TextLooped()
The code which runs in response to the event is not contained within the class, but
rather the Form where the object variable is located. A separate Event Sub
procedure must be created in the Form and the object variable must include the
WithEvents keyword.
'Form general declarations
Private WithEvents scrMessage As ScrollingText

'Event procedure contained in the Form


Private Sub scrMessage_TextLooped()

MsgBox "Text is looping"

End Sub

Here's a sample of how the class could be created and manipulated from a
Form_Load procedure.

'general declarations
Private WithEvents scrMessage As ScrollingText

Private Form_Load()

Set scrMessage = New ScrollingText


With scrMessage
.BeginX = 0
.BeginY = 120

141
.Caption = "Your scrolling message here"
Set .Parent = Me
.Speed = 50
.ScrollLoop = True
.Direction = stUp
.Color = RGB(0, 128, 255)
.StartScroll
End With

End Sub

If needed, you can create multiple instances or arrays of object variables to


represent class modules, but remember to reclaim memory used by the object
variables.

Private Sub Form_Unload()


Set scrMessage = Nothing
End Sub
Initialize and Terminate events

These are the only two events included with a class module. Uses for these events
are to set default values for your properties, to keep track of how many instances
are running and to destroy objects which are being used as properties. To keep an
instance count, add a Public counter variable to a Form in your project.

'Form general declarations


Public InstanceCount As Integer

'Class module events


Private Sub Class_Initialize()

Caption = "Default message"


Form1.InstanceCount = Form1.InstanceCount + 1

End Sub

Private Sub Class_Terminate()

Set Parent = Nothing


Form1.InstanceCount = Form1.InstanceCount - 1

End Sub
Have you created an InstanceCount property of Form1? Sure, any Public variable in a
Form or Class is actually a Property (without property procedures).

Page 43: Bit Block Transfer


View Page Comments Leave a Comment

Bit Block Transfer is a set of powerful drawing functions which transfer a block of bits
(a bitmap) from one device context to another. Forms, PictureBoxes, and the Printer
object are device contexts and all have an hDC property, which is the Windows
handle to the device.

VB's PaintPicture method can handle bitmap transfer, but is slightly slower than
accessing API directly. The most often used API call for this task is the BitBlt
function.

142
Declare Function BitBlt Lib "gdi32" Alias _
"BitBlt" (ByVal hDestDC As Long, _
ByVal x As Long, ByVal y As Long, _
ByVal nWidth As Long, ByVal nHeight _
As Long, ByVal hSrcDC As Long, ByVal _
xSrc As Long, ByVal ySrc As Long, _
ByVal dwRop As Long) As Long
Let's look at all these arguments.

• hDestDc - Handle to the destination device context, (where the bitmap will be
transferred to). You simply use the hDC property of a Form, PictureBox, or
Printer object.
• x and y - Left and Top coordinates within the destination device context of
where the bitmap will be transferred to.
• nWidth and nHeight - Width and Height of the bitmap being transferred. You
can use the ScaleWidth and ScaleHeight property of the source if you are
transferring it's entire contents.
• hSrcDC - Handle to the source device context, (where the bitmap is located).
Use the hDC property.
• xSrc and ySrc - Left and Top coordinates within the source device context of
where to get the bitmap. Usually set to 0 unless you want only a portion of
the source.
• dwRop - Raster operation (ROP) to perform. There are 255 ROP's available.
Your API Text Viewer defines 11 constants for the most often used ROP's.
SRCCOPY is the easiest to use and performs a simple copy action.

Public Const SRCCOPY = &HCC0020

***The SRCCOPY constant might throw you for a loop. Remember that the bitmap is
being transferred, not actually copied where it remains in the source. Set the source
device context's AutoRedraw property to True.

***If the destination device context is set to AutoRedraw, you must use the Refresh
method after using BitBlt.

***If a PictureBox with a Picture object is the source, set the AutoSize property to
True to have quick access to the correct nWidth and nHeight.

***Make sure the ScaleMode property of both the destination and source is set to 3 -
Pixels. When using API drawing functions, measurements are always in pixels.

Here's a routine which will tile a background picture across a Form. A PictureBox was
drawn, named picSource, AutoSize and AutoRedraw were set to True, ScaleMode
was set to pixels and a picture was inserted. The Form's ClipControls property was
set to False for quicker re-painting of the background.

Private Sub Form_Paint()

Dim x As Integer, y As Integer

With picSource
For x% = 0 To ScaleWidth Step .ScaleWidth
For y% = 0 To ScaleHeight Step .ScaleHeight
BitBlt hdc, x%, y%, .ScaleWidth, _
.ScaleHeight, .hdc, 0, 0, SRCCOPY
Next y%

143
Next x%
End With

End Sub
Raster Operations

The above example simply copies the bitmap to the destination with no regard to
what was already there, but it's also possible to combine the source with the
destination in a variety of ways.

Each pixel of a bitmap is comprised of a certain number of bits depending on the


capabilities of the device. For instance, a 16 color display is achieved with 4 bits.
Raster operations use one or more of four logical operations on corresponding bits
from the source and destination bitmaps. You are probably already familiar with
these four logical operators - NOT, AND, OR, XOR

Take a look at this below and try to recall using these logical operators with If
statements, remembering that 1 is True and 0 is False.

NOT 0 = 1 (NOT False = True)


NOT 1 = 0 (NOT True = False)
NOT 0101 = 1010 (NOT Magenta = Light Green)

0 AND 0 = 0
0 AND 1 = 0 (True only if both are True)
1 AND 0 = 0
1 AND 1 = 1
0011 AND 1100 = 0000 (Cyan AND Light Red = Black)

0 OR 0 = 0
0 OR 1 = 1 (True if either is True)
1 OR 0 = 1
1 OR 1 = 1
0001 OR 1000 = 1001 (Blue OR Gray = Light Blue)

0 XOR 0 = 0
0 XOR 1 = 1 (True if one, and only one is True)
1 XOR 0 = 1
1 XOR 1 = 0
1011 XOR 1100 = 0111 (Light Cyan XOR Light Red = Off White)

• Public Const SRCCOPY = &HCC0020


Destination = Source
Destination takes on the appearance of the source.

• Public Const SRCPAINT = &HEE0086


Destination = Source OR Destination
Source is logically OR'ed onto the destination.

144
• Public Const SRCAND = &H8800C6
Destination = Source AND Destination
Source is logically AND'ed onto the destination.

• Public Const SRCINVERT = &H660046


Destination = Source XOR Destination
Source is logically XOR'ed onto the destination.

• Public Const NOTSRCCOPY = &H330008


Destination = NOT Source
Source is inverted, then copied onto the destination.

• Public Const NOTSRCERASE = &H1100A6


Destination = (NOT Source) AND (NOT Destination)
Source and destination are inverted, then logically AND'ed together.

145
• Public Const MERGEPAINT = &HBB0226
Destination = (NOT Source) OR Destination
Source is inverted, then logically OR'ed onto the destination.

• Public Const DSTINVERT = &H550009


Destination = NOT Destination
Destination is inverted. Source does not factor into the result.

• Public Const BLACKNESS = &H42


Destination = 0
Destination becomes black.
• Public Const WHITENESS = &HFF0062
Destination = 1
Destination becomes white.

Most of these results probably don't look very useful. A programmer who is an expert
with Boolean algebra will use a series of complex ROP's to create some nice effects.

The best example I can give is how to draw an image transparently onto a
background. This actually requires two images - a monochrome (black and white)
mask image, and the color image itself.

Look at the SRCAND result and see that when the color white is logically AND'ed onto
the destination nothing changes, in other words, transparency. Set up your mask
image to be white on the background and black in the actual image area because
White AND Any Color = The Same Color

Your actual color image should have a black background and will be logically OR'ed
onto the destination (SRCPAINT). This will keep the background transparent and the
image area will retain it's original colors because
Black OR Any Color = The Same Color

Suppose I had these two images in PictureBoxes named picMask and picImage.

146
And this background on a Form named frmMain.

These two calls to BitBlt...

BitBlt frmMain.hdc, 20, 20, 32, 32, picMask.hDC, 0, 0, SRCAND


BitBlt frmMain.hdc, 20, 20, 32, 32, picImage.hDC, 0, 0, SRCPAINT
...would produce this result.

StretchBlt
Declare Function StretchBlt Lib "gdi32" _
Alias "StretchBlt" (ByVal hdc As _
Long, ByVal x As Long, ByVal y As _
Long, ByVal nWidth As Long, ByVal _
nHeight As Long, ByVal hSrcDC As _
Long, ByVal xSrc As Long, ByVal _
ySrc As Long, ByVal nSrcWidth As _
Long, ByVal nSrcHeight As Long, _
ByVal dwRop As Long) As Long
Use this function to shrink or enlarge the bitmap being transferred. The arguments
are basically the same as BitBlt with the addition of two more - you must specify the
width and height of both the source and the destination.

The following example uses the picture from an invisible PictureBox as the source
and a Form's background as the destination. Every time the Form is painted or re-
painted, the picture is stretched to fill the Form's entire area. The ClipControls
property of the Form should remain at the default value of True in order to redraw
the entire background when needed.

Private Sub Form_Paint()

StretchBlt hdc, 0, 0, ScaleWidth, ScaleHeight, _


picBlt.hdc, 0, 0, picBlt.ScaleWidth, _
picBlt.ScaleHeight, SRCCOPY

End Sub

Private Sub Form_Resize()

Refresh

End Sub
Resizing a Form won't trigger a Paint event unless some new portion of the Form is
exposed, so we make sure the background is re-drawn on each resize by using the
Form's Refresh method.

StretchBlt can also be used to flip or mirror a bitmap. This is done by using negative
values for nWidth and/or nHeight.

147
• Set nWidth to a negative value to mirror the bitmap.
• Set nHeight to a negative value to flip the bitmap.
• Set both nWidth and nHeight to negative values to flip and mirror.

Flipping and mirroring will cause the bitmap to be drawn above and to the left of the
destination coordinates, rather than below and right, so you may need to adjust the
x and y parameters in this scenario.

Even though we are always referring to bitmaps, this doesn't mean that non-BMP file
formats will not work with BitBlt. You can also insert GIF's, JPG's, ICO's, etc. into the
source device context. But in the case of Icon files with transparent backgrounds,
you are better off using the DrawIcon function.

DrawIcon
Declare Function DrawIcon Lib "user32" _
Alias "DrawIcon" (ByVal hdc As _
Long, ByVal x As Long, ByVal y _
As Long, ByVal hIcon As Long) As Long
Supply the destination device context (hdc), the upper left coordinates (x and y),
and the Windows handle to the Icon. This handle can be obtained from the Picture
property.
DrawIcon hdc, 0, 0, picIcon.Picture
Icons can also be stretched. DrawIconEx is the Icon equivalent to StretchBlt.
Declare Function DrawIconEx Lib "user32" _
Alias "DrawIconEx" (ByVal hdc As _
Long, ByVal xLeft As Long, ByVal _
yTop As Long, ByVal hIcon As Long, _
ByVal cxWidth As Long, ByVal cyWidth _
As Long, ByVal istepIfAniCur As Long, _
ByVal hbrFlickerFreeDraw As Long, _
ByVal diFlags As Long) As Long

• cxWidth and cyWidth - The Icon will be stretched to fit this size
• iStepIfAniCur - The index of a specific frame to draw, only used with animated
cursors, otherwise set to 0.
• hbrFlickerFreeDraw - Set to 0
• diFlags - can be set to one of the following constants:

Public Const DI_MASK = 1


Draws only the monochrome mask associated with the Icon.

Public Const DI_IMAGE = 2


Draws only the image (no transparent area)

Public Const DI_NORMAL = 3


Draws the Icon normally (both mask and image)

DrawIconEx hdc, 0, 0, picIcon.Picture, _


100, 100, 0, 0, DI_NORMAL

Page 44: The Device Context


View Page Comments Leave a Comment

148
Always use the pixel coordinate system!

Drawing cannot take place without a device context (DC). VB creates and manages
device contexts for you in the form of PictureBoxes, Forms, and the Printer object.
You can access these DC's with the hDC property.

You won't manipulate the attributes of the DC itself, but rather the GDI objects
associated with it. A DC stores one (and only one) of each of the following objects.

• Bitmap
• Brush
• Pen
• Font
• Region
• Path
• Palette

You are indirectly telling VB what objects are to be used with a DC when you set
properties such as Picture, Palette, BackColor, DrawMode, DrawWidth, Font,
ClipControls, etc.

A more professional approach is to avoid the overhead of VB-created DC's and


manage all the objects yourself by using a memory device context.

All DC's are not created equal. The first step in setting up a memory DC is to realize
that you need a DC which is compatible with your project:

Declare Function CreateCompatibleDC Lib "gdi32" _


Alias "CreateCompatibleDC" (ByVal hdc As Long) As Long

The one argument in this function will be the hDC property of any Form or
PictureBox in your project. The return value (myDC) will be the Windows handle to
the DC, or 0 if an error occured.

Dim myDC As Long


myDC = CreateCompatibleDC(frmMain.hdc)

You cannot yet draw onto this DC because there is nothing to draw on. In other
words, the memory DC must have a memory bitmap and this bitmap must also be
compatible with your project.

Declare Function CreateCompatibleBitmap Lib "gdi32" _


Alias "CreateCompatibleBitmap" (ByVal hdc As _
Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long

Think about how large of a bitmap you'll need, and then supply those dimensions to
the function. Again, the return value will be the Windows handle to the bitmap, or 0
upon error.

Dim myBM As Long


myBM = CreateCompatibleBitmap(frmMain.hdc, 640, 480)

149
Guess what? You still can't draw with this DC because it knows nothing about the
bitmap you created. Any objects you create must be selected into the DC.

Declare Function SelectObject Lib "gdi32" _


Alias "SelectObject" (ByVal hdc As Long, _
ByVal hObject As Long) As Long

You have the handle to the DC (myDC), and you have the handle to the bitmap
(myBM).

SelectObject myDC, myBM


Pen object

The Pen object governs how lines are drawn within a DC. A default solid black Pen
one pixel wide will be automatically selected into a memory DC when it is created.
Change the Pen's width, color, or style with the following function:

Declare Function CreatePen Lib "gdi32" _


Alias "CreatePen" (ByVal nPenStyle As _
Long, ByVal nWidth As Long, ByVal crColor As Long) As Long

Your API Text Viewer supplies constants to be used for the nPenStyle parameter:

Public Const PS_SOLID = 0


Public Const PS_DASH = 1
Public Const PS_DOT = 2
Public Const PS_DASHDOT = 3
Public Const PS_DASHDOTDOT = 4
Public Const PS_NULL = 5 'Invisible pen

The return value is the Windows handle to the Pen object which is used to select the
Pen into the DC.

'Create a dashed red pen 5 pixels wide


Dim myPen As Long
myPen = CreatePen(PS_DASH, 5, vbRed)
SelectObject myDC, myPen
Brush object

A DC's Brush determines how areas are filled. These fills can be solid, patterned, or
hatched. A default solid white Brush is automatically selected into a memory DC
when it is created.

Declare Function CreateSolidBrush Lib "gdi32" _


Alias "CreateSolidBrush" (ByVal crColor As Long) As Long

Declare Function CreateHatchBrush Lib "gdi32" _


Alias "CreateHatchBrush" (ByVal nIndex As _
Long, ByVal crColor As Long) As Long

Declare Function CreatePatternBrush Lib "gdi32" _


Alias "CreatePatternBrush" (ByVal hBitmap As Long) As Long
Creating a solid Brush simply involves supplying the color value.
Dim mySolidBrush As Long
mySolidBrush = CreateSolidBrush(RGB(192, 0, 255))
SelectObject myDC, mySolidBrush

150
When creating a hatched Brush, you'll use one of these constants found in the API
Text Viewer and a color value.

Public Const HS_HORIZONTAL = 0


Public Const HS_VERTICAL = 1
Public Const HS_FDIAGONAL = 2
Public Const HS_BDIAGONAL = 3
Public Const HS_CROSS = 4
Public Const HS_DIAGCROSS = 5

Dim myHatchBrush As Long


myHatchBrush = CreateHatchBrush(HS_HORIZONTAL, vbBlue)
SelectObject myDC, myHatchBrush

A pattern Brush is based on a bitmap. You can use the Picture property as the
hBitmap parameter. Important to remember is that only the upper left 8 x 8 pixel
area of the bitmap is used for the pattern.

Dim myPatternBrush As Long


myPatternBrush = CreatePatternBrush(Picture1.Picture)
SelectObject myDC, myPatternBrush
Reclaim GDI memory!

If you want to play with DC's, you're going to have to be a big boy and clean up after
yourself. GDI memory will vanish quickly unless you do this correctly. There are two
ways to clean up - one I will explain briefly, the other is my preferred way.

There are two important end results here. The first is to restore the DC to it's original
state before you started messing around with it. The second is to delete any objects
you created when you're done with them.

Declare Function DeleteObject Lib "gdi32" _


Alias "DeleteObject" (ByVal hObject As Long) As Long

You might have noticed I always discard the return value of SelectObject. This
function does actually return a useful value - the handle to the previously selected
object of that type. Some will use these handles to backtrack through any changes
made to the DC and select the original objects back in, then delete the objects.

I suggest using the SaveDC function before selecting any objects into the DC, then
as the first order of a "clean up" routine, use the RestoreDC function to put the DC
into the proper state for deletion.

Declare Function SaveDC Lib "gdi32" _


Alias "SaveDC" (ByVal hdc As Long) As Long

151
Declare Function RestoreDC Lib "gdi32" _
Alias "RestoreDC" (ByVal hdc As Long, _
ByVal nSavedDC As Long) As Long
Here is all the code in it's proper order for creating, manipulating, and deleting a
memory DC.
Dim mySavedDC As Long
Dim myDC As Long
Dim myBM As Long
Dim myPen As Long
Dim myHatchBrush As Long
myDC = CreateCompatibleDC(frmMain.hdc)
myBM = CreateCompatibleBitmap(frmMain.hdc, 640, 480)
myPen = CreatePen(PS_DASH, 5, vbRed)
myHatchBrush = CreateHatchBrush(HS_HORIZONTAL, vbBlue)
mySavedDC = SaveDC(myDC)
SelectObject myDC, myBM
SelectObject myDC, myPen
SelectObject myDC, myHatchBrush

'Draw on this DC and do


bit block transfers
to your heart's content

RestoreDC myDC, mySavedDC


DeleteObject myBM
DeleteObject myPen
DeleteObject myHatchBrush
DeleteObject myDC

Page 45: API Rectangles


View Page Comments Leave a Comment

Always use the pixel coordinate system!

If it's on the screen, it's in a window. Basically, every window occupies a rectangular
area. Most VB controls are also windows based on a rectangle. Though it is possible
to create irregular shaped Forms and controls, these are merely windows with
transparent areas which still define a rectangular area. Hence the importance of
learning to deal with rectangles.

First off, you must be familiar with the RECT structure. Structures are added to a
project by copying pre-written Type statements from the API Text Viewer to the
general declarations section of a Form, Class, or code module. Like Declare
statements, Types must be Private if used in a Form or Class. Structures and Types
are synonomous with User Defined Types and are affectionately known as structs.

Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type

A RECT struct is built to represent some rectangular area of the screen or a window.
The Left and Top elements are set to the upper left coordinates of the rectangle and
the Right and Bottom elements are set to the lower right coordinates. The Sub
procedure below builds a RECT struct based on where a control is located on a Form.

152
'Structs cannot be passed ByVal
Public Sub RectFromControl(ctlRect As RECT, ByVal ctl As Control)

With ctlRect
.Left = ctl.Left
.Top = ctl.Top
.Right = .Left + ctl.Width
.Bottom = .Top + ctl.Height
End With

End Sub

'Get the rectangle which Image1 occupies


Dim rc As RECT
RectFromControl rc, Image1
Optionally, you could use the SetRect API function to build your RECT structs.
Declare Function SetRect Lib "user32" Alias _
"SetRect" (lpRect As RECT, ByVal X1 As Long, ByVal _
Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
The RectFromControl Sub would then look like this:
Public Sub RectFromControl(ctlRect As RECT, ByVal ctl As Control)

With ctl
SetRect ctlRect, .Left, .Top, .Left + .Width, .Top + .Height
End With

End Sub

OK, big deal, we have the coordinates of where a control is located on the screen.
Well, how would you like to able to tell if two controls were intersecting with each
other? Ah yes, collision detection, what every VB game programmer needs. These
two API rectangle functions will do the trick.

Declare Function IntersectRect Lib "user32" _


Alias "IntersectRect" (lpDestRect As RECT, _
lpSrc1Rect As RECT, lpSrc2Rect As RECT) As Long

Declare Function IsRectEmpty Lib "user32" _


Alias "IsRectEmpty" (lpRect As RECT) As Long

Give the IntersectRect function an empty RECT (lpDestRect) and two more RECT
structs (lpSrc1Rect and lpSrc2Rect) built with RectFromControl. If lpDestRect is still
empty after the function completes, there is no intersection between the two
controls. The IsRectEmpty function will return 0 if the lpRect argument is not empty.

Dim testRect As Rect


Dim ctl1Rect As Rect
Dim ctl2Rect As Rect
RectFromControl ctl1Rect, Image1
RectFromControl ctl2Rect, Image2
IntersectRect testRect, ctl1Rect, ctl2Rect
If IsRectEmpty(testRect) = 0 Then
'Image1 and Image2 are colliding
End If

It's also easy to find out if a single point on the screen lies within a certain rectangle.
This could be used to detect "hotspots" on a graphic.

Declare Function PtInRect Lib "user32" _


(lpRect As RECT, ByVal ptx As Long, _
ByVal pty As Long) As Long
Note: Your API Text Viewer may have a different Declare statement for the PtInRect
function. The above Declare is correct.

153
Suppose we added a background graphic to a Form and wanted a particular 5 x 5
pixel area to be a hotspot.

Private Sub Form_MouseDown(Button As Integer, _


Shift As Integer, X As Single, Y As Single)

Dim rcHotspot As RECT


SetRect rcHotspot, 10, 20, 15, 25
If PtInRect(rcHotspot, CLng(X), CLng(Y)) Then
MsgBox "You clicked the secret hotspot!" 'Or whatever, LOL
End If

End Sub
More API Rectangle functions

If you need to move a RECT relative to it's current location, you could use:

Declare Function OffsetRect Lib "user32" _


Alias "OffsetRect" (lpRect As RECT, ByVal _
x As Long, ByVal y As Long) As Long
If x is negative - rectangle moves to the left
If x is positive - rectangle moves to the right
If y is negative - rectangle moves up
If y is positive - rectangle moves down
Dim rc As Rect
SetRect rc, 100, 100, 200, 200
OffsetRect rc, -50, -50
RECT is moved to 50, 50, 150, 150

Enlarge or shrink the size of a RECT while maintaining it's upper left coordinates.

Declare Function InflateRect Lib "user32" _


Alias "InflateRect" (lpRect As RECT, ByVal _
x As Long, ByVal y As Long) As Long
If x is negative - rectangle's width is reduced
If x is positive - rectangle's width is increased
If y is negative - rectangle's height is reduced
If y is positive - rectangle's height is increased
'Double the size of a RECT
Dim rc As Rect
Dim rcWidth As Long
Dim rcHeight As Long
SetRect rc, 100, 100, 200, 200
With rc
rcWidth = .Right - .Left
rcHeight =.Bottom - .Top
End With
InflateRect rc, rcWidth, rcHeight

Find out if two RECT structs are equal in size and location.

Declare Function EqualRect Lib "user32" _


Alias "EqualRect" (lpRect1 As RECT, _
lpRect2 As RECT) As Long
This is a Boolean function like IsRectEmpty. The return value is non-zero if the two
RECT's are exact.

Force one RECT struct to be equal in size and location to another.

154
Declare Function CopyRect Lib "user32" _
Alias "CopyRect" (lpDestRect As RECT, _
lpSourceRect As RECT) As Long
Left, Top, Right, and Bottom elements of lpDestRect are set to the corresponding
elements of lpSourceRect.

Force a RECT struct to be empty.

Declare Function SetRectEmpty Lib "user32" _


Alias "SetRectEmpty" (lpRect As RECT) As Long
Left, Top, Right, and Bottom elements all become zero.

Create a larger RECT which is just big enough to contain two other RECT structs.

Declare Function UnionRect Lib "user32" _


Alias "UnionRect" (lpDestRect As RECT, _
lpSrc1Rect As RECT, lpSrc2Rect As RECT) As Long
Give UnionRect an empty RECT (lpDestRect) and two other defined RECT structs.

Page 46: VB vs Javascript


View Page Comments Leave a Comment
What is Javascript?

Javascript is not a programming language for creating standalone applications like


VB. It would better described as a scripting language used only in internet
documents (web pages). Javascript is never compiled...it is embedded as text into
HTML code and interpreted when the web page is loaded. Like VB, Javascript can be
contained in Functions (no Subs) and called from HTML events.

Why do I need Javascript?

If you're not planning on developing for the internet, then you don't need this. This
page is aimed at Visual Basic coders who want to make the jump to interactive
and/or database-driven web sites done with Active Server Pages. Granted, Javascript
is not a requirement to get into ASP, but the well-rounded internet programmer
knows that Javascript is executed client-side within the browser. This means that
many tasks, such as form validation, can be done quicker and without reloading the
page.

Note: This rest of this page will describe how to convert VB code to Javascript. I am
going to assume that you already have a basic knowledge of HTML documents and
the different ways to embed Javascript into them.
An entirely different syntax

Find the curly braces {} keys on your keyboard. You're gonna need them often. As in
VB, a good portion of Javascript code is written in blocks which are begun and ended
by a curly brace set.

In VB:

If x = 99 Then

155
x = 0
Else
x = x + 1
End If

In Javascript:
if (x == 99) {
x = 0;
} else {
x++;
}

In Javascript, we surround the condition for an if statement in parentheses. Say


goodbye to the keywords Then and End If. Notice how different areas of the
statement are cordoned off with sets of curly braces. It doesn't really matter where
the curly braces are...the same block could have been written like:
if (x == 99) {x = 0;}
else {x++;}

Even though this is valid, most Javascript programmers will not format their code
like this. For readability, it's better to line break after the opening curly brace and
indent code in the right places.

In VB, you can put multiple statements on one line by separating them with a colon.
In Javascript, the same is true but with a semi-colon. It's not required to terminate a
line with a semi-colon as I have done above, but it's a good habit to get into. Other
languages which have syntax similar to Javascript (PHP for example) WILL require a
semi-colon line terminator.

Wondering about that doubles equals sign used in the if condition? That's the
Javascript operator for comparing for equality. The single equals sign is used only for
assigning a value. Good old VB lets you use an equals sign for BOTH comparing AND
assigning.

Old faithful VB also doesn't care if your code is upper or lower case, but Javascript
will shoot you if you tried using If instead of if. Most Javascript keywords and
functions are written in lower case.

One more thing to note about the code above is x++. The ++ operator will
increment a number by 1. Although the statement x = x + 1 is valid in Javascript,
most of the time you won't see it written that way.

Javascript functions vs. VB functions

Remember...the only difference between VB Subs and Functions is that Subs can't
return a value. VB Functions can return a value, but still you can ignore that return
value if you want. The same is true of Javascript functions...they run a series of
statements and return a value. If you don't need a return value, just return the value
of True to indicate to yourself that the function completed itself.

Here's a function in VB which rounds up a number:

Function Ceiling(TheNumber As Single) As Long


Ceiling = CLng(Number + 0.5)
MsgBox "Last line in the function" 'Will execute
End Function

156
The same function in Javascript:
function ceiling(thenumber) {
return Math.ceil(thenumber);
alert("Last line in the function"); //Will not execute!
}

The biggest difference here is in the way the return value is assigned to the function.
In VB, you assign the return value directly to the name of the function, but in
Javascript you use the return statement. Using the return statement also exits the
function, so the alert on the last line will not run.
Common VB functions as Javascript

OK, enough yak yak. If you're anything like me, you learn by example. Below you
will find common VB functions converted to Javascript. These functions are called
exactly as they would be in VB. The function names, parameters and return values
are also identical. Some error messages have been added for dealing with invalid
parameter values.

Note: In VB, the first parameter of InStr and InStrRev is optional. Not so with the
Javascript functions of the same name below.

You'll probably want to study these functions with a Javascript reference nearby.

Download all the code as js2vb.js

function LCase(Value) {
return Value.toString().toLowerCase();
}

function UCase(Value) {
return Value.toString().toUpperCase();
}

function Len(Expression) {
return Expression.toString().length;
}

function Left(Str, Length) {


if (Length < 0) {
alert("Invalid Length argument\n\nLeft function (js2vb.js)"); return "";
}
return Str.substring(0, Length);
}

function Right(Str, Length) {


if (Length < 0) {
alert("Invalid Length argument\n\nRight function (js2vb.js)"); return "";
}
return Str.substring(Len(Str) - Length, Len(Str));
}

function Mid(Str, Start, Length) {


if (Length < 1) {
alert("Invalid Length argument\n\nMid function (js2vb.js)"); return "";
}
if (Start < 0) {
alert("Invalid Start argument\n\nMid function (js2vb.js)"); return "";
}
return Str.substring(Start, Start + Length);
}

function InStr(Start, String1, String2, Compare) {

157
if (Start < 1) {
alert("Invalid Start argument\n\nInStr function (js2vb.js)"); return "";
}
if (Start > Len(String1)) return 0;
if (Len(String2) == 0) return Start;
if (Compare == 1) {String1 = LCase(String1); String2 = LCase(String2);}
if (Start > 1) {
var index = Right(String1, Len(String1) - Start + 1).indexOf(String2)
if (index == -1) {return 0;} else {return index + Start;}
} else {
return String1.indexOf(String2) + 1
}
}

function InStrRev(StringCheck, StringMatch, Start, Compare) {


if (Start == 0 || Start < -1) {
alert("Invalid Start argument\n\nInStrRev function (js2vb.js)");
return "";
}
if (Len(StringMatch) == 0) return Start;
if (Compare == 1) {
StringCheck = LCase(StringCheck); StringMatch = LCase(StringMatch);
}
if (Start > 1) {
return Left(StringCheck, Start).lastIndexOf(StringMatch) + 1;
} else {
return StringCheck.lastIndexOf(StringMatch) + 1;
}
}

function IsNull(Expression) {
return (Expression == null);
}

function IsEmpty(Expression) {
return (Expression.toString().length == 0);
}

function IsObject(Expression) {
return (typeof Expression == "object");
}

function IsArray(VarName) {
return (VarName.constructor.toString().indexOf("Array") == -1);
}

function IsDate(Expression) {
var test = new Date(Date.parse(Expression));
return !(isNaN(test.getYear()));
}

function MonthName(Month, Abbreviate) {


var months = new Array("January","February","March","April",\
"May","June","July","August","September","October","November","December");
if (Month < 1 || Month > 13) {
alert("Invalid Month argument\n\nMonthName function (js2vb.js)");
return "";
}
var retval = months[Month - 1];
if (Abbreviate) retval = Left(retval, 3);
return retval;
}

function WeekdayName(Weekday, Abbreviate, FirstDayOfWeekValue) {


var weekdays = new Array("Sunday","Monday","Tuesday","Wednesday",\
"Thursday","Friday","Saturday");
if (Weekday < 1 || Weekday > 7) {
alert("Invalid Weekday argument\n\nWeekdayName function (js2vb.js)");
return "";
}
if (FirstDayOfWeekValue < 0 || FirstDayOfWeekValue > 7) {

158
alert("Invalid FirstDayOfWeekValue argument\n\n\
WeekdayName function (js2vb.js)");
return "";
}
var addval = (FirstDayOfWeekValue > 1) ? FirstDayOfWeekValue : 0;
if (Weekday + addval > 7) addval -= 7;
return weekdays[Weekday + addval - 1];
}

Note: A single backslash is Javascript's line continuation character.

159

Você também pode gostar