Beruflich Dokumente
Kultur Dokumente
Ken Levy
58 My Favorite Feature
We asked for input from several early adopters of VFP 8 about
their favorite new features. You'll sense their enthusiasm as
you read their suggestions for why you should step up to the
latest and greatest version.
The Community
Departments
65 Code Compilers
49 Advertisers Index
4 code-focus
Eric Rudder
6 code-focus
Eric Rudder
Senior Vice President, Developer
and Platform Evangelism
Microsoft
Eric Rudder currently serves as senior vice president of Developer and Platform Evangelism. He
is responsible for leading the companys
outreach to the developer community. Through
his leadership of this division, Rudder and his
team focus on coordinating the overall programming model for the client and server, creating
the best tools for the .NET platform and fostering
synergies between Windows and the .NET Enterprise Server offerings. In addition, the division
will be chartered with evangelizing the extended
Microsoft platform through a variety of content
offerings including providing effective webbased training for developers and IT managers.
Prior to leading the Developer and Platform
Evangelism division, Rudder was vice president
of Technical Strategy working directly with
Chairman and Chief Software Architect Bill Gates
on Microsofts technical planning processes. He
also has worked in several other areas at
Microsoft, including networking, operating
systems and developer tools, where he previously served as general manager for Visual
Studio.
code-focus
Ken Levy
Design-time
Software
Run-time
8 code-focus
Ken Levy
Visual FoxPro Product Manager
Microsoft
code-focus
Claudio Lassala
claudio@eps-software.com
Insert-SQL changes
The Empty class does not have any intrinsic properties, methods or events (yes, thats
why it is called empty). Not havFast Facts
ing any members, this class gets
instantiated and destroyed very
There are so many new features in
quickly. Of course, this class is
VFP 8 that we cant cover them all.
useless as-is, because if it doesnt
have an AddProperty method like
Claudio does his best to hit the
all the baseclasses in VFP, how
high points in this round-up of
can we make any use of it?
several exciting new capabilities
For example, if you have an object (very likely a business object) that among its PEMs has properties that
match fields in a cursor, you can insert data into the
cursor right from the object. The syntax is very trivial:
Insert into curProducts from name oProduct
Note that oProduct is the name of the business object that has the data. This feature lets you avoid the
need to append a blank record to the cursor and
then fill the fields with the gather command, as in:
The answer is two more new features: the AddProperty and RemoveProperty functions. Despite the fact that these
functions can work with any object, their biggest
role is to support the Empty class. The use of these
functions and the Empty class is very trivial:
oCustomer = CreateObject("Empty")
AddProperty(oMonths, "LastName")
AddProperty(oMonths, "FirstName")
oCustomer.LastName = "Lassala"
? oCustomer.LastName
An Empty object?
For a long time, developers have been looking for
a lightweight class, mainly when they have to add
and remove properties to an object on the fly.
Some people have been using the Session or the
Custom classes, but those are not really intended
to be lightweight classes. Others use classes like
Relation or Line, but that is kind of weird from an
OOP point of view, because a Relation class is
10 code-focus
RemoveProperty(oMonths, "LastName")
Lets now see a sample that plays with the new features
both in the Insert-SQL and the Scatter commands.
Often, we need to keep track of the size and location of forms as they were the last time they were
destroyed (the user runs a form, changes its size and
location, then closes the form). The next time the
user runs the form, he expects that it will be shown
the same way.
Randy Brown
Lead Program Manager,
Visual FoxPro
Microsoft
After we shipped Visual FoxPro
7.0, the VFP team sat down and
spent quite a few weeks
reviewing over 1000 enhancement requests (ERs) that we
had collected in our internal
Foxwish database. This database contains ERs from a
variety of sources included past
betas, Universal Thread,
internal, old aliases, user
groups, conferences, etc. We
had certain goals, objectives
and criteria in mind when we
evaluated the ERs.
In the end, because the FoxPro
community has become so
diverse in how folks develop
apps, we felt that it was critical
to maintain a strong mix of
features so that VFP 8.0 would
contain something for
everyone. We wanted VFP 8.0
to be a no brainier upgrade
and we think we have come
pretty close to achieving this.
And if we add some new
customers to the FoxPro
community, even better. Note:
we estimate that over 80% of
the features in VFP 8.0 are from
ERs submitted by you guys in
the community. You could say
that this is certainly the
peoples FoxPro.
code-focus
11
GDI+ Support
VFP 8 has support for GDI+.
That means that we can use the
most traditional image formats
in those properties that store
reference to an image file (like
the Picture property). Inclusively, we can use an Animated
GIF, getting rid of specialized
OCX or the Web Browser
control just to show that kind of
image.
If the image is inside an Image
control, we can also do things
like Rotate and Flip the image
(through the use of the RotateFlip property of the Image
control).
Windows XP Themes
Support
VFP 8 supports Windows XP
Themes by default. In other
words, our VFP applications
can have a very modern look,
following the skin set to the
Operating System, without even
a single line of code. But if in
any case you want to control
the Themes capability, you can
set that with a good granularity,
in an application level, form
level or control level.
oDE = CreateObject("CustomersOrders")
oDE.OpenTables()
Table changes
One of the long-waited features that now is present
in VFP 8 is the AutoIncrementing field for tables
(usually used for keeping primary keys in a table).
VFP has a new field type called Integer (AutoInc)
that addresses this issue.
There is also a minor enhancement for the indexes
of a table: a collating sequence can be defined individually for every single index tag.
Another very neat new feature is that an expression
can be defined as the Caption of a field in the Table
Designer. This expression is evaluated every time a
Browse window is opened. However, theres something much cooler about this. In earlier versions, we
could drag a field from a DataEnvironment and drop
it onto a form, and that would create a label with its
Caption property set to the caption of the field.
In VFP 8, the caption of the label will be evaluated
at run-time, which gives us the flexibility for datadriven labels in our forms. For example, I could set
the Caption of the CompanyName field to the
following expression:
=Localize("CompanyName")
12 code-focus
Grid Enhancements
It is common knowledge that VFPs Grid is one of
the most powerful and most used controls. It is easy
to use and provides great functionality. However,
VFP developers have always dreamed of some
enhancements to the Grid to make it even better. In
VFP 8, several dreams have come true. Figure 4
shows some of the features that I will explain below.
VFPs Grid now behaves just the same. You can see
in Figure 4, between the Contact Name and
Contact Title columheaders, the mousepointer
indicating the divider line. After double clicking, the
Contact Name column is resized.
Not just that: we can also Auto-fit columns in the
grid programmatically:
*-- Resize second column of the grid
Thisform.grdCustomers.AutoFit(2)
Grid Highlighting
VFP developers have always had trouble working with
highlighted rows in a grid. End-users want the ability to
select a row in a grid and keep track visually of which
row is selected, even when the focus is in another place
(see the third line of the grid in Figure 4). We usually
end up writing a whole bunch of code to make that
work. Now, this line of code is all we need:
Thisform.grdCustomer.HighlightStyle = 2
Also, we have two properties to configure the backcolor and forecolor of the highlighted row: HighlightBackColor and HighlightForeColor.
A Grid as a Listbox
Previously, if we clicked in a row of the grid, the
row would be highlighted and the cursor would be
blinking inside the cell (usually in a textbox). Sometimes we do not want the user to have access to the
cell. Rather, we just want the user selecting a
record, imitating a Listbox (remember that a
Listbox does not has the Grids capabilities to bind
to data, among other things).
To address that, there is another welcomed new
property to the Grid: AllowCellSelection.
*-- Do not allow the user to select cells.
Thisform.grdCustomers.AllowCellSelection = .F.
Centering Checkboxes
Often, grids are used to allow the user to select one
or multiple records (look at the Select column in
Figure 4). In those situations, we insert a Checkbox
in a column of the Grid, and guess what? We
cannot make the Checkbox center itself within the
column. The workaround was to insert the
Checkbox centered in a Container, and then insert
the container into the column.
Once again, we can reduce that now to a single line
of code by using the Centered property of the
Checkbox (I did split it here into three lines to fit
the layout of the magazine):
*-- Centralize the Checkbox the eighth column.
With Thisform.grdCostumers
.Columns(8).chkSelect.Centered = .T.
EndWith
Summary
Although I could go on and on with dozens more
examples of new features, space is limited in this
magazine. So, get your hands on a copy of VFP 8
and dig right in. Youll soon be as excited as I am.
Taskbar Notification
Area (the System
tray)
The Solutions Samples bring a
Systray Class. This class lets
the developer add items (icons)
to the System Tray in a very
easy way, wrapping up the
complex calls to the Windows
API. Its possible to add a
regular VFP menu to the item in
the Systray, or even use those
balloon tips that we see in
Windows XP (if the application
is running in that OS).
Docking Windows
support
VFP 7 brought the ability to
manually dock windows in its
IDE. Now VFP 8 brings the
ability to do that programmatically in the IDE, with the new
ADockState function and the
new DOCK WINDOW
command.
The ADockState function
creates an array with the status
for the dockable IDE windows
and toolbars. The DOCK
WINDOW is self explanatory,
and you can find its syntax in
the documentation.
Claudio Lassala
code-focus
13
Doug Hennig
dhennig@stonefield.com
lnForms = alen(This.aForms, 1) 1
lnCols = alen(This.aForms, 2)
if lnForms = 0
This.aForms = .NULL.
else
* lnForm is the row number of the closed form
adel(This.aForms, lnForm)
dimension This.aForms[lnForms, lnCols]
endif
Fast Facts
Visual FoxPro 8.0 includes a
new Collection base class.
In this article, Doug describes
some uses for collections and why
they are superior to arrays.
14 code-focus
* Array-based code
lnPos = ascan(This.aForms, CustomerForm)
lnRow = asubscript(This.aForms, lnPos, 1)
lnInstance = This.aForms[lnRow, 4]
* Collection-based code
lnInstance = ;
This.oForms(CustomerForm).nInstance
Calvin Hsia
Tables(customer).Fields.Count
Summary
The new VFP 8 Collection base class makes it easy
to create and work with collections of items. While
arrays still have their place, I predict collections will
replace them in the majority of uses in applications
as VFP developers become more familiar with them.
Doug Hennig
Suppose you want to work with meta data.
Wouldnt it be nice to retrieve the data type for a
field using code like the following?
Lead Developer,
Visual FoxPro Team
Microsoft
Collections have been desired
and emulated in previous
versions of VFP by developers
whove seen them in other
languages. Now in VFP 8.0, the
new Collection base class
allows you to write better code
with improved performance.
Not only is the Puzzle back in
VFP 8.0, but the new TaskPane
contains a Minesweeper game
written in FoxPro which I
wrote and uses the new
Collection class. Cool!
Tables(Products).Fields(ProductID).DataType
.Decimals = CoreMeta.nDecimals
.Binary
= CoreMeta.lBinary
.AllowNulls = CoreMeta.lNull
.Caption
= trim(CoreMeta.cCaption)
endwith
This.Item(lcTable).Fields.Add(loField, lcField)
endcase
endscan
use in CoreMeta
endproc
enddefine
case CoreMeta.cRecType = F
lcTable = juststem(CoreMeta.cObjectNam)
lcField =
trim(justext(CoreMeta.cObjectNam))
loField = createobject(Field)
with loField
.DataType = CoreMeta.cType
.Length
= CoreMeta.nSize
define class
DataType
Length
Decimals
Binary
AllowNulls
Caption
enddefine
Field as Custom
=
= 0
= 0
= .F.
= .F.
=
code-focus
15
megger@eps-software.com
Events Explained
For Visual FoxPro developers,
an event and the code that
reacts to it are one and the
same thing. But thats actually
incorrect. An event is a very
different animal from the code
that runs when an event
occurs. Lets look at a simple
example.
Markus Egger
16 code-focus
We could do so by using the new BindEvent() function. We could use that function whenever and
wherever we want. In this example, the forms Init()
method might be the best place:
FUNCTION Init()
BINDEVENT(THIS.command1,"Click",;
Unbinding Events
Just like we bound event handlers to events, we can
also unbind events. So lets assume we want our
ShowMethod() to only handle the very first Click
event the button raises, but not subsequent ones. In
that case, we could add the following code to the
ShowMessage() method:
FUNCTION ShowMessage()
MessageBox("Button Clicked")
UnbindEvents(THIS.command1,"Click",;
THIS,"ShowMessage")
ENDFUNC
THIS,"ShowMessage")
ENDFUNC
Mike Stewart
Test Engineer,
Visual FoxPro Team
Microsoft
The new event binding features
in VFP 8.0 are probably one of
the most powerful new functions added to our latest
version. You can now hook any
method of any object to the
event of any VFP object,
including the ability to chain
multiple object methods
together for one event. This
allows you to extend the functionality of the code of any
event method to do anything
you want at runtime by simply
hooking on any object
method to that event method.
You can even raise an event on
any custom or base method of
a VFP object. The more you
work with event binding in VFP
8.0, the more youll find new
things you can do with VFP like
never before.
code-focus
17
Built-In Hooks
18 code-focus
gets
called,
Conclusion
BindEvent() is a very powerful new feature. It is
probably not a feature that every developer has
been waiting for, but those of you who have a need
for this feature need it badly.
If you have any questions about this feature, feel
free to send me an email.
Markus Egger
Introducing the
CursorAdapter Class
One of the most exciting new features of Visual FoxPro 8 is the
CursorAdapter class, which provides a common interface for
working with data from many different sources. Chuck takes you with
him on an adventure in exploring how to use CursorAdapter to change the way you
relate to data in VFP 8, whether native tables, ODBC, OLE DB, or XML.
Chuck Urwiler
chuck@eps-software.com
20 code-focus
Next, click the Cursors page, which is initially empty. Under the list box, choose the New button to
create a new CursorAdapter class with the
CursorAdapter Builder. Initially, you will see the
Properties page, providing options for choosing the
name of the class and the alias of the cursor created
by the class.
Alan Griver
Group Manager,
Visual Studio Data Team
Microsoft
One of the many areas we
enhanced in VFP 8.0 is access
to different data sources. The
new CursorAdapter class is the
best way to achieve unified data
access to native VFP tables,
XML, ADO.NET, SQL Server,
and ODBC or OLE DB data
sources. This class combines
many of the great remote view
and SQL PassThrough capabilities that youre used to from
prior versions of VFP while
providing many new capabilities. Based on feedback weve
received from VFP developers,
the CursorAdapter class is one
of the most popular new
features in VFP 8.0.
code-focus
21
Is the cursor a
member object of the
CursorAdapter?
A possible misconception you
may have is that the cursor is a
member object of the CursorAdapter. It is not. Instead, it is
just like any other VFP cursor in
that it is not encapsulated
within any object. This means
that you cannot pass a CursorAdapter object reference
between two applications (or
application tiers) and expect the
cursor to follow instead, it
will exist only where the cursor
was created.
Therefore, if you wish to pass
the data retrieved by a
CursorAdapter as a parameter
to another application or
process, youll have to convert
the data into something like an
XML document or ADO
recordset for the data to be
properly marshaled to the
receiving end.
All of the properties that contain See Init are populated at run time by the code generated for the Init
method. That code is shown in Listing 1.
This is probably the most educational place to look
after the builder is finished to see how the properties have been set. Note that you can change these
values here or through the builder. However, by
changing them here, you run the risk of breaking
functionality, as your property changes are not verified as they are within the builder.
Property or Method
Value
Description
Alias
AutoOpen()
cCustomer
<code>
BufferModeOverride
Flags
Init()
5 Optimistic
Table Buffering
CUSTOMERID
C(5),
0
<code>
KeyFieldList
See Init
Name
SelectCmd
caCustomer
See Init
Tables
UpdatableFieldList
UpdateNameList
CUSTOMERS
See Init
See Init
UseDEDataSource
.T. True
CursorSchema
22 code-focus
code-focus
23
table name in this listing. However, without this exact format, your updates will fail, but without errors.
Ill reiterate this point later when creating a class
without the builder.
Earlier I stated that the CursorAdapter, using the
Native data source, is essentially a replacement for a
Local View. If you have ever built a local view, you
probably can see the similarities: a SQL Select statement is constructed, you specify which fields you
wish to be updatable, you specify the field or fields
that comprise the primary key, and then let VFP do
the rest. Once you retrieve the data in the cursor,
you can use TableUpdate() to send the changes back
to the base table, and VFP automatically builds the
necessary Update, Insert or Delete statements to
carry out the modifications.
As an example, recall the earlier Replace statement
that changed the value of the Contact field in the
cCustomer alias. Upon issuing the TableUpdate
statement, VFP automatically generates (and sub-
24 code-focus
Handling Errors
Obviously, not everything will go as planned when
trying to update data from the CursorAdapter. As
you well know, TableUpdate can fail for a variety of
reasons, such as an update conflict or a record lock.
Do you have to do anything special with the
CursorAdapter class to detect these problems? The
answer is, it depends.
Lets create a simple update problem by locking the
record that the CursorAdapter is attempting to
update. If the class designer is still open, close it.
Then, instantiate the deTest class with the NewObject function, just as you did above, and call the
OpenTables method. Browse the cursor so that you
can see the data, but dont change anything yet.
Now open a second instance of VFP 8 so you can
lock the record. Execute the following lines in the
command window to lock the record that youll
attempt to update:
OPEN DATABASE (HOME(2)+"Northwind\northwind.dbc")
USE customers
LOCATE FOR customerid = CACTU
?RLOCK()
code-focus
25
26 code-focus
CursorAdapter vs.
Local View
One advantage that a
CursorAdapter has over the
Local View is the lack of a DBC.
Since the view is a DBC object,
it can only be contained within
the DBC. Views can be a
problem in applications where
lots of users attempt concurrent access to the same view
VFP acquires a short lock on
the view record in the DBC, and
sometimes this presents
unexpected errors. Without the
need for a DBC, CursorAdapters
will never suffer from this
problem.
28 code-focus
IF nUpdateType = 1 THEN
cUpdateInsertCmd = cUpdateInsertCmd + ;
" if @@ROWCOUNT = 0 "+ ;
"RAISERROR(Update Failed due to update " + ;
"conflict.,16,1)"
ENDIF
Now, for every row that is sent to the back end, this
code will test to see if the row was updated. If not,
VFP will receive the error, TableUpdate will fail,
and AError will show the usual 1526 error with the
message text as specified.
There are two problems with this approach. First,
this is a specific fix for SQL Server; for other ODBC
data sources (such as Oracle), this code will not
work. Second, this error message is very generic as
it always generates the same VFP error number, and
makes proper error handling from VFP a bit difficult. This issue can be mitigated somewhat by
creating custom error messages on the SQL Server,
each with their own unique error number.
Another way to improve upon this solution is to use
Stored Procedures to perform the updates instead of
letting VFP build and pass an ad-hoc query to the
server. Of course, the tradeoff of adopting the stored
procedure approach is that you lose the benefit of
having VFP automatically handle the updates.
Parameterization
As a side note, you have now seen one way to parameterize the commands for a CursorAdapter class.
In essence, every event that occurs in the class has
a set of Before and After methods, such as BeforeUpdate and AfterUpdate. However, there is no
BeforeSelect or AfterSelect instead, these are
called BeforeCursorFill and AfterCursorFill, since
the cursor is filled with the result of the SelectCmd.
The BeforeCursorFill method receives three parameters, and expects a Boolean return value. The
first parameter, lUseCursorSchema, specifies
whether the CursorSchema property controls the
construction of the resultant cursor or not. The
second parameter, lNoDataOnLoad, is similar to
the NODATA clause on views, where the schema is
retrieved but no data is actually passed from the
data source.
For the current discussion, the third parameter,
cSelectCmd, is of primary interest. It is also
passed by reference (like the cUpdateInsertCmd
parameter of BeforeUpdate) and is initially populated with the current setting of SelectCmd.
However, if you change the value of this parameter, it does not change the value of the
Parameterization, Part II
If you have used views or SQL Pass Through in the
past, then you are probably familiar with parameterization by using the ? character in front of a
variable. As you might suspect, this feature still
works in the CursorAdapter. The following example
code shows how you can use a parameter in the
SelectCmd property of a CursorAdapter:
This.SelectCmd = "SELECT * FROM Customers " + ;
" WHERE CompanyName like ?lcMyVar "
lcMyVar = C%
This.CursorFill()
code-focus
29
Batch Updates
Let me give you a few words of
caution about our OLE DB
update code. If you have previously used ADO, you may be
used to setting the LockType
property to adLockBatchOptimistic instead of adLockOptimistic,as we did in our OLE DB
update example. However, if
you set it differently, you must
manually invoke the UpdateBatch method of the RecordSet
object.
You can perform batch
updates with an ADO-based
CursorAdapter, but you must
set the RecordSets LockType
property to adLockBatchOptimistic and use Optimistic Table
buffering. Then, allow your user
to make the batch of changes
to the cursor. When you wish
to update the data source, you
first call TableUpdate, which
updates only the RecordSet and
does not post the changes to
the back end. Once TableUpdate succeeds, then you must
invoke the UpdateBatch method
of the RecordSet to actually
update the data source.
30 code-focus
goCAADO = CREATEOBJECT(caADO)
BROWSE
DEFINE CLASS caADO AS CursorAdapter
oConn = NULL
oRS = NULL
Alias = "cCustADO"
DataSourceType = "ADO"
SelectCmd = "SELECT " + ;
"CustomerID, CompanyName, ContactName, "+;
"ContactTitle, Address, City, Country "+;
"FROM Customers WHERE Customerid LIKE C%"
FUNCTION Init()
This.DataSource = this.oRS
This.oRS.ActiveConnection = this.oConn
This.CursorFill()
ENDFUNC
ENDDEFINE
code-focus
31
32 code-focus
KeyFieldList = "CustomerID"
UpdatableFieldList = ;
"CompanyName, ContactName, ContactTitle, "+ ;
"Address, City, Country"
UpdateNameList = ;
"CustomerID Customers.CustomerID, " + ;
"CompanyName Customers.CompanyName, " + ;
"ContactName Customers.ContactName, "+;
"ContactTitle Customers.ContactTitle, " + ;
"Address Customers.Address, "+;
"City Customers.City, Country Customers.Country"
Tables = "Customers"
34 code-focus
code-focus
35
36 code-focus
Final Thoughts
In this article, weve covered a lot of ground,
showing the four faces of the new CursorAdapter
class. Youve seen how to build the CursorAdapter
through the DataEnvironment and CursorAdapter
builders, through the visual class designer, and
through a PRG. Youve also seen the basics of
building CursorAdapter classes for native, ODBC,
OLE DB or XML data access, and how to make
each one of these classes updatable as well.
The next step is to think about how to apply these
classes to your everyday development efforts. In my
opinion, I can see the CursorAdapter class working
very well in the UI layer of any application, and also
in certain kinds of business objects where there is
lots of processing code to implement. The
CursorAdapter, as noted earlier, is not a good
choice of object for passing data between tiers, as it
converts everything into a non-portable VFP cursor.
However, in a scenario where a business object uses
a CursorAdapter class, it can receive the data from
the data source, and then process that data using
standard VFP commands and functions, since it is
in a VFP cursor. When finished, that data could be
converted to a more suitable type for cross-tier
marshalling, such as XML.
The other advantage of the CursorAdapter is the
common OOP interface, regardless of the type of
data that it accesses. Even with the XML version,
which requires the most coding to make updatable,
we still retrieved the data using CursorFill, updated
data with TableUpdate, and retrieved errors with
AError, as with every other type of CursorAdapter.
With a little forethought and planning, you could
conceivably build a reusable set of classes, based
upon the CursorAdapter, that could then be
tweaked for each individual data source. These
classes could be reused between applications or
mixed within the same application to standardize
the way your application handles data.
Chuck Urwiler
Structured Error
Handling in VFP 8
Markus Egger
megger@eps-software.com
ON ERROR DO ErrorHandler
38 code-focus
Fast Facts
code-focus
39
40 code-focus
This is an acceptable solution, but there are difficulties with this approach. First of all, the method
might call out to other methods that may reset the
error handler or point to a different handler. This is
a problem that is hard to avoid, since you may not
have control over other code that is running.
Also, at a later point in time, someone may want to
add an Error() method to this object (perhaps to
handle errors that may occur in other methods).
The problem with that is that the error method
takes precedence over the ON ERROR handler,
hence rendering the ON ERROR useless.
Introducing: Try/Catch
To solve these issues, Visual FoxPro 8.0 introduces
Structured Error Handling. This approach allows
the developer to wrap a series of commands into a
block that is handled by a local error handler. The
advantage of this error handler is that it usually
TRY
CATCH
lReturnValue = .F.
ENDTRY
RETURN lReturnValue
ENDFUNC
ENDDEFINE
As we can see, this is a much simpler way to implement the solution. First of all, it is simply much less
kludgy and is a very clean implementation. But
more importantly, it is a much superior implementation from a technical point of view. The solution
is not influenced by outside error handling.
Also, we have full control over what is to happen
if an error does occur. Unlike in the example with
the error event, we can write code within our
method that executes no matter whether an error
occurred or not, making it easy to set the return
value to our liking. (We were able to do this in the
previous example, but the solution was error prone
and easy to break by running it in different environments).
Im sure that by now you already have a good idea
about what Try/Catch does: Whatever code we run
inside a Try-block will execute until an error
occurs. If an error does in fact occur, the Catchblock is executed. Note that the try block stops
executing as soon as an error occurs. There is no
way to retry or ignore the error. If thats what you
would like to do, Try/Catch error handling is not
the right solution.
FINALLY
IF VarType(oWord) = "O"
oWord.Application.Quit()
ENDIF
ENDTRY
RETURN lReturnValue
ENDFUNC
ENDDEFINE
In this example, we shut down Word, even if something went wrong. Note however, that the error may
have occurred before Word ever got instantiated.
Therefore we need to first check whether Word is
an object. (Actually, things may be a little trickier
with automation objects, especially Word, but for
simplicity well leave it at that.)
At this point you may wonder why we need a
Finally-block. After all, we could have put that code
after the ENDTRY and would have achieved an
identical result. However, there are scenarios that
can greatly benefit from using the finally-block
(which we will examine further down), making the
use of FINALLY a good idea in general.
Esther Fan
User Education Writer,
Visual FoxPro Team
Microsoft
There are many great new
features in Visual FoxPro 8.0!
Possibly the most popular of
them all is the new TRY/CATCH
structured error handling,
similar to various .NET and
other programming languages.
In addition to new features in
VFP 8.0, we made many documentation improvements.
Community feedback is important, so each VFP 8.0 Help topic
now includes an e-mail hyperlink labeled, "Send feedback on
this topic to Microsoft." You
can now send your comments
and suggestions about the VFP
8.0 documentation to us
directly so we can make even
more improvements in the
future.
code-focus
41
Try/Catch in .NET
Visual Studio .NET uses a very
similar mechanism as Visual
FoxPro 8.0. As one would
expect, due to .NETs integral
thread-safety, the mechanism
there is a little more structured,
but also a little less flexible. For
instance, VFP8 can throw any
variable or object as a custom
error. In .NET, only exception
objects can be thrown. On the
other hand, .NET doesnt differentiate between user errors and
system errors, which has
advantages as well.
TRY
USE Customer
LOCATE FOR LastName = "Gates"
IF FOUND()
StrToFile("Gates found!","customer.log")
ENDIF
CATCH
TRY
StrToFile("Error: "+Message(),"Error.log")
CATCH
* Nothing we can do now
ENDTRY
FINALLY
IF Used("Customer")
USE IN Customer
ENDIF
ENDTRY
42 code-focus
This will make an object named oException available within the Catch-block. This object has a
number of properties, such as ErrorNo, Message,
LineNo, Details, LineContents, and more. Using this
construct, we can use the following syntax to check
for errors caused by the template only:
FUNCTION Export(lcText1,lcText2)
LOCAL lReturnValue
lReturnValue = .T.
TRY
* We run the regular code
LOCAL oWord as Word.Application
oWord = CREATEOBJECT("Word.Application")
oWord.Application.Visible = .T.
TRY
oWord.Documents.Add("MyTemplate.dot")
CATCH TO oException
IF oException.ErrorNo = 1429
oWord.Documents.Add()
ELSE
* We have a different problem
THROW oException
ENDIF
ENDTRY
oWord.Selection.InsertAfter(lcText1)
oWord.Selection.InsertAfter(lcText2)
CATCH
lReturnValue = .F.
ENDTRY
RETURN lReturnValue
ENDFUNC
This will catch only error 1429. All other errors will
be automatically elevated to the outer error handler,
if there is one. Otherwise, the default VFP error
dialog would be shown. Therefore, this is a shortcut
that is functionally identical to the version shown in
the previous example (except that the exception
elevated to the outer handler will not be a user error).
What makes this feature very powerful is that there
can be a number of different catch-blocks:
TRY
oWord.Documents.Add("MyTemplate.dot")
CATCH TO oEx WHEN oEx.ErrorNo = 1429
oWord.Documents.Add("MyOtherTemplate.doc")
CATCH TO oEx WHEN oEx.ErrorNo = 1943
MessageBox("Stop closing Word!!!")
CATCH
MessageBox("Something else happened!")
ENDTRY
code-focus
43
FUNCTION Init(lcDetailedMessage)
THIS.ErrorDetail = lcDetailedMessage
ENDFUNC
ENDDEFINE
44 code-focus
IF oEx.UserValue.ErrorNo = 10001
The Try/Catch wraps a simple call to another function (or method). That function apparently has its
In this example, we define an ON ERROR statement within a Try/Catch block (xxxxx always
code-focus
45
Finally
46 code-focus
THROW oEx
ENDTRY
ENDFUNC
FUNCTION Error(nError, cMethod, nLine)
MESSAGEBOX(MESSAGE())
ENDFUNC
ENDDEFINE
Conclusion
Structured Error Handling is one of the most
important language enhancements Visual FoxPro
has seen in a while. It is very powerful and helps
you tremendously in your attempts to produce
bullet-proof code.
If you have any questions about this technology, feel
free to email me.
Markus Egger
Member Classes
Bring Flexibility
Garrett Fitzgerald
garrett@donnael.com
Fast Facts
By changing the way you can define
and work with member classes,
Microsoft has given VFP developers
tremendous flexibility in working
with pageframes and grids.
n Visual FoxPro, there are certain classes that have
meaning only when theyre in containers: for
example, Pages and Headers can live only in PageFrame and Column objects. In versions before VFP 8,
if we wanted to subclass these classes, we needed to
manually add them using the AddObject method.
But, there were problems with this approach. For
example, this kept us from putting controls on a
subclassed page in the Form Designer.
Where to Define
Member Classes
In all these examples, Ive used
classes defined in PRGs,
instead of VCXs. With Pages,
OptionButtons, and CommandButtons, you can define them
either way. Headers and
Columns, though, can be
defined only in PRGs.
48 code-focus
PROCEDURE Activate
IF This.lRefresh
This.Refresh()
ENDIF
ENDPROC
ENDDEFINE
Advertisers Index
CoDe Magazine
www.code-magazine.com
DevTeach
www.devteach.com
EPS Software Corp.
www.eps-software.com
Essential Fox Conference
www.essentialfox.com
F1 Technologies
www.f1tech.com
Hentzenwerke Publishing
www.hentzenwerke.com
Oak Leaf Enterprises
www.oakleafsd.com
Resoulution
www.xcase.com
Soft Classics, Ltd.
www.CodeMine.com
Stonefield Systems Group
www.stonefield.com
TakeNote Technologies
www.takenote.com
47, 53
37
2, 7, 29
19
Universal Thread
www.universalthread.com
Vision Data Solutions
www.visionds.com
West Wind Technologies
www.west-wind.com
Wizards and Builders Gmbh
www.wizards-builders.com
59
39
57, 71
5, 63
31
72
23
Advertising Sales:
33
61
27
43, 45
49
50 code-focus
XMLAdapter, XMLTable,
The
XMLAdapter
class
Cathi explains how this and
provides new support for
and XMLField Classes
related new classes open new
working with XML. One capapossibilities
for
your
applications.
bility of this new class is
The XMLAdapter Class allows
support for hierarchical XML.
you to load XML from an XML
This means that an XML file
source, parse the XML Schema (when it exists), and
that represents a collection of different and potenadd one or more XMLTable object(s) to its tables
tially related tables, such as a Windows .NET
collection. In turn one or more XMLField object(s)
DataSet, will render into separate Visual FoxPro
are added to the fields collection of each
cursors.
XMLTable.
Flexibility and control over data is enhanced by
XMLAdapter class can also create an XML docubeing able to control the schema of the XML that
ment representing the contained tables and fields
is created, as well as control the data types that
that have been populated. XMLAdapter class
the cursor creates from the XML of the schema.
includes two other child member classes:
This allows you to load in the XML, change the
XMLTable and XMLField. These provide the ability
schema, then generate the cursor. In addition,
to walk through the schema programmatically and
you can take a cursor in memory, control the
access or set information.
schema, then generate the XML in a different
format.
The primary functionality that the XMLAdapter
Class provides is to retrieve XML via the
Working with XML in Visual FoxPro 8.0
LoadXML() method, then parse the XML via the
vs. Visual FoxPro 7.0
contained XML Schema, as appropriate, into one or
more XMLTable objects, which in turn contain
The XMLTOCURSOR() / CURSORTOXML() funcXMLField object(s).
tions that were new to Visual FoxPro 7.0 restricted
you to working with XML files that contained data
The XMLTable class is a collection of all tables
for only one table. If more than one table is
contained in the XML and functions to allow you to
contained in the XML file, you need to parse
step through the table to perform procedures on
through the file manually. In addition, you dont
them.
have the full control of the schema that was
contained in the XML file to change the data types
The collection of XMLTable objects describes the
before converting the XML to a Visual FoxPro
XML as a Visual FoxPro cursor or cursors, along
cursor. When the XML was generated from the
with any relational information. The XMLAdapter
cursor, you dont have control of the schema that is
does not store the actual XML schema or content,
generated.
but does store object references to them.
The developer may then use the XMLTable.ToCursor() method to produce a cursor that contains the
data of all the fields represented by the child
member XMLField collection.
The XML and XML Schema data retrieved via the
XMLAdapter.LoadXML() method remains in
memory until replaced via a subsequent call to
LoadXML(), or when it is specifically released by
calling the ReleaseXML() method.
The XMLField class is a collection created for
each XMLTable and contains all the fields in the
table. The developer can iterate through the field
objects and make any necessary changes. There
are no methods associated with the XMLField
class.
Aleksey Tsingauz
Developer,
Visual FoxPro Team
Microsoft
The new XMLAdapter class in
VFP 8.0 converts XML to VFP
cursors and vice versa in a
much easier and more flexible
way than the VFP 7.0 CursorToXML and XMLToCursor functions. This new class includes
support for converting back
and forth between hierarchical
XML and multiple cursors as
well as the ability to preserve or
apply changes via .NET DiffGrams. This allows even
greater compatibility and functionality for VFP developers
when working with remote
data, SQL Server, and XML
Web services. With the
XMLAdapter class, working
with XML in VFP has never
been more flexible, easy,
and fun.
code-focus
51
52 code-focus
Integrating business
applications has become an
essential need. XML is the key
to this kind of integration.
54 code-focus
Return MyDataSet
code-focus
55
UpdateGrams and
DiffGrams
SQLXML can also be used to
output XML in updategram
format. Visual FoxPro 7.0
provided support for updategrams with the XMLUPDATEGRAM function. The new
CursorAdapter class in Visual
FoxPro 8.0 also supports
updategrams.
XMLAdapter Solution
Sample Utility
Visual FoxPro 8.0 provides a
solution sample that allows you
to work with XML using the
XMLAdapter class. This tool
can be used to test various
property settings and to
generate various types of XML.
You can load XML files for
compatibility testing with the
XMLAdapter class. The solution sample is called Loading
and Generating XML Using
XMLAdapter Class. To open
the solution sample, use the
new Task Pane Manager.
56 code-focus
Summary
XML provides a universal way of describing,
exchanging and moving data over the
Intranet/Internet. It is platform-neutral. XML is a
basis for Windows .NET technologies. The strategic
value of Visual FoxPro 8.0 supporting and
embracing XML is that it gives Visual FoxPro the
capability to connect to other Visual FoxPro applications and Windows .NET applications, as well as
other platforms or systems that support XML
universally integrating business applications.
Cathi Gero
code-focus
57
My Favorite Feature
When you first begin using the new Visual FoxPro 8, you are sure
to find useful new features that will make your development tasks
easier. Several members of the Visual FoxPro developer community who have already worked with VFP 8 tell us their opinions of the best and most useful new features. Perhaps their answers will help guide you to some cool ideas you can put to
work right away.
Richard Stanton
Developer,
Visual FoxPro Team
Microsoft
Beyond new features, product
stability was one area we really
focused on for Visual FoxPro
8.0. We fixed many bugs that
existed in previous versions of
FoxPro. In addition to coding
some of the great new VFP 8.0
features, I spent a great deal of
time fixing bugs reported in the
beta and from previous
versions of VFP. We are confident that VFP 8.0 is the most
stable release ever, and it is a
great upgrade from VFP 7.0
SP1. Besides, I always think of
stability as a feature.
58 code-focus
Tamar E. Granor
Technical Editor, FoxPro Advisor
Author, Whats New in Visual FoxPro 8
Mark McCasland
US Environmental Protection Agency
M & J Software
www.mctweedle.com
The Code References tool provides a projectoriented search-and-replace tool. It also provides
the engine for the new View Definition item on the
context menu of editing windows, which lets you
quickly move to the definition of the highlighted
item (variable, property, method, class, constant,
etc.). Ive already used this tool extensively to
Even if you are not yet developing for a SQL dataexplore the code of all three tools, as well as for
base, the CursorAdapter will
searching in a multi-developer
allow you to specify a NATIVE
client project. It makes underFast Facts
DataSourceType so you can
standing the structure of a
handle your VFP data like a
project much easier.
We asked for input from several
SQL database. Then, if you
early adopters of VFP 8 about
ever have to upsize the dataThe Toolbox strikes me as a
their favorite new features.
base to SQL Server or Oracle,
marriage of the Form Controls
Youll sense their enthusiasm as
all you have to do is change the
toolbar and the Component
you read their suggestions for
*DataSource and *DataSourceGallery. Its more capable than
why you should step up to the
Type properties in each Cursothe toolbar and much easier to
rAdapter.
understand and use than the
latest and greatest version.
Gallery. It provides a home for
Get used to seeing the *Propall the controls you use, as well
erty
shorthand
notation
as for blocks of text (such as a
because there are three prefixes associated with
standard header). The ability to add my own cateeach suffix preceded by an asterisk. They prefixes
gories and items means that I can put everything Ill
are always INSERT, UPDATE and DELETE. For
need for a given project in one easy-to-find place.
example, *Cmd is short for the InsertCmd,
UpdateCmd and DeleteCmd properties. Then there
Im most intrigued by the possibilities of the Task
is the same trio set for *CmdDataSource and
Pane Manager. This tool provides a portal for VFP
*CmdDataSourceType.
development. The panes provided include a way to
centralize access to all my regular development
I can now create a generic subclass of this
tools, offer quick access to sample code, let me
object which I will then use to create a subclass
organize paths and settings, and more. But the Task
for each remote table in a database. In fact, develPane Manager doesnt stop there. You can add
oping a builder to do this for you is quite simple.
third-party panes and create your own. Ive already
Using my builder, I loop through an array of
added a pane giving me quick access to my
tables and get the column and data type meta data
Hentzenwerke e-books, and another that displays
from the SQL database for each table, which I
my own website. I expect the VFP 8 versions of
then use to populate the following properties for
most VFP tools to include custom task panes, so
the Adapter:
you can switch between them easily.
Alias
ConversionFunc
KeyFieldList
SelectCmd
Tables
UpdatableFieldList
UpdateNameList
I cheat a little for the KeyFieldList property because
ALL my tables use a surrogate integer field for the
primary key, and it is always the same name,
KEYID. However, I can also retrieve the PK
column for any table from the SQL database to
make this even more generic.
The ConversionFunc property lets you specify what
conversion function to perform on a particular field.
For example, for VARCHAR data types, VFP in the
past would send data right padded with spaces.
Well, that defeated the purpose of a VARCHAR
column since all these spaces were not RTRIMmed
before being sent to the database unless you
handled all the INSERT or UPDATE SQL yourself.
Now, you can list such fields and how to format the
data before it goes to the database. For example:
loAdapter.ConversionFunc = ;
[LastName RTRIM, FirstName RTRIM]
code-focus
59
60 code-focus
Backward Compatibility
Is A Great Feature
Doug Dodge
ddodge@utah-inter.net
What has become my perennial favorite feature
about Visual FoxPro (or even FoxPro for that
matter) is that when migrating between different
versions there has been relatively little that has
changed. Oh, sure, there have been changes and
even sometimes core VFP engine behavior has
changed, but it has always seemed to be on the
margins for me.
In our particular case we have an amalgam of code
that dates at times back almost twelve (12) years.
62 code-focus
John Koziol
Test Engineer,
Visual FoxPro Team
Microsoft
The Tips and Tricks here are
just a few of the great ideas
weve seen from folks whove
had a chance to look at Visual
FoxPro 8.0. Wed love to see
more tips, samples, and utility
downloads for VFP 8.0 on the
various community sites soon.
Chin Bae
Objective Micro Technologies, Inc.
cbae@objectivemicro.com
ave you ever wanted to force a particular
column of a query result set to be a specific data
type? In prior versions of VFP, one of the few
tricks that you can use is to wrap a PADR() around a
character expression to force it to be certain length.
Also, you could add $0 to any numeric expression to
force a numeric column to be a currency data type.
Fast Facts
Listing 1: Chin Baes example for casting different data types with the CursorAdapter.
***test.prg
PUBLIC oCA, aFld[1]
CLEAR
OPEN DATABASE HOME(2) + data\testdata.dbc
oCA = CREATEOBJECT(CATest)
AFIELDS(aFld, oXC.Alias)
? aFld[1,1], aFld[1,2], aFld[1,3]
? aFld[2,1], aFld[2,2], aFld[2,3]
? aFld[3,1], aFld[3,2], aFld[3,3]
? aFld[4,1], aFld[4,2], aFld[4,3]
? aFld[5,1], aFld[5,2], aFld[5,3]
DEFINE CLASS CATest as CursorAdapter
DataSourceType = Native
Alias = crsTest
*!* Use CursorSchema to do the following:
*!* Widen cust_id from 6 characters to 20
*!* Cast cnt as integer
*!* Cast avg as currency
*!* Cast the original numeric type of qty as a character
64 code-focus
INTO
INTO
INTO
INTO
INTO
memberships
memberships
memberships
memberships
memberships
VALUES
VALUES
VALUES
VALUES
VALUES
(0)
(14)
(0)
(0)
(14)
Code Compilers
Publisher
EPS Software Corp., Publishing Division
13810 Champion Forest Dr., Suite 202
Houston, TX 77069 USA
Phone: 281-866-7444
Fax: 281-866-7466
Rod Paddock, Editor in Chief
Markus Egger, Co-Publisher
Rick Strahl, Co-Publisher
David Stevenson, Associate Publisher
Ellen Whitney, Managing Editor
Technical Reviewers
Markus Egger
David Stevenson
Mike Yaeger
Siriusware, Inc.
myaeger@siriusware.com
Production
Doug Dodge
Garrett Fitzgerald
Tamar Granor
Ryan Katri
Paul Maskens
Barbara Peisch
Rick Strahl
Cindy Winegarden
Rod Paddock
Printing
Roto Longo Ag.
Advertising Sales
Tammy Ferguson
tammy@code-magazine.com
David Stevenson
david@code-magazine.com
Erna Egger
erna@code-magazine.com
Subscriptions
Subscribe online at
www.code-magazine.com
Subscription problems?
subscriptions@code-magazine.com
Online
www.code-magazine.com
code-focus
65
Creating a Statusbar
Control with VFP 8
Rick Strahl
rstrahl@west-wind.com
I use the StatusBar control in almost all of my applications and in many of them it has serious timing
problems with form rendering. The result is that the
66 code-focus
Heres how the end result works: The class is implemented as a VFP Container class, which builds a
panel collection and then dynamically renders the
collections content into various dynamically added
form controls.
To use the control, you first you drop the wwStatusBar control onto a form. The next step is to define
the panels, which you can add to the Init method of
either the form or the wwStatusBar class. Listing 1
uses the latter.
Figure 1: A nice themed VFP application with a StatusBar control thats stuck
in Windows Classic mode
Style Description
0
1
2
3
Figure 2: A Themes enabled VFP application using the wwStatusBar control for
an XP compliant look.
loPanel = THISFORM.oStatus.Panels(2)
loPanel.Text = "New Text"
loPanel.Icon = "bmp\ClassMethod.gif"
loPanel.Width = 300
THISFORM.oStatus.RenderPanels()
If you dont modify the size of the Panel you can use
the RenderPanel(lnPanel) method which is more efficient. Anytime the size of a panel changes however,
the entire status bar must be redrawn by calling
RenderPanels().
By default the StatusBar can automatically resize
itself and stay anchored to the bottom of the form.
Note that at design time the status bar just sits
anywhere on the form, but at runtime the Resize()
method knows how to automatically resize the
StatusBar. If lAutoResize is .T. wwStatusBar uses
BindEvent() to hook the parent containers Resize
event and automatically resizes when the form is
code-focus
67
68 code-focus
Manifest Files
Figure 3: Three different modes are available for the wwStatusBar: 1 - XP Style, 2 - Classic Style and 3- Modified Classic.
late Statusbar operation. Its made of disabled, nonthemed, transparent textbox controls and a few
images that make up the sizing grips and separators,
which are placed onto the container in just the right
order. The new VFP Collection class is used to
manage the panels. The process begins with the
AddPanel method (Listing 2), which creates a new
object to add to the Panels Collection:
AddPanel adds a new item to the Panels Collection,
but it doesnt render anything yet. Collections make
life much easier when you are building lists like the
Panels here. In VFP 7.0 I might have used an array,
which is more work to size and then parse and retrieve
values from. With a collection, the process of adding
and retrieving items from the list is much easier.
IF this.lAutoResize
BINDEVENT(THIS.Parent,"Resize",THIS,"Resize")
ENDIF
ELSE
loLabel.Width = lnWidth
ENDIF
IF this.nDisplayMode = 2
*** 3D Box no shadow must be closer to top
loLabel.Top = 1
loLabel.BorderStyle = 1
ELSE
loLabel.Top = 4
loLabel.BorderStyle = 0
ENDIF
ELSE
loLabel = Evaluate("THIS.Panel" + Transform(x))
Endif
*** Inherit Font
loLabel.FontName
loLabel.FontSize
loLabel.FontBold
from container
= This.FontName
= This.FontSize
= This.FontBold
loLabel.Value = loPanel.Text
IF llFirstRender
loLabel.Left = THIS.nRenderPosition
ENDIF
loLabel.Alignment = loPanel.Align
loLabel.Visible = .T.
lnWidth = loPanel.Width - 2
IF lnWidth < 1
loLabel.Width = 1
" + lolabel.value
THIS.nRenderPosition = THIS.nRenderPosition +
loLabel.Width + 1
*** Paint XP style separator after all but last
panel
If llFirstRender AND this.nDisplayMode # 2 AND ;
x < This.Panels.Count
If Type("THIS.PanelSep" + Transform(x)) # "O"
This.AddObject("PanelSep" +
Transform(x),"Image")
Endif
loImage = Evaluate("THIS.PanelSep" + Transform(x))
loImage.Left = THIS.nRenderPosition
loImage.Top = 5
THIS.nRenderPosition = THIS.nRenderPosition + 2
loImage.Picture = this.cXPSeparatorPicture
loImage.Visible = .T.
Endif
Collections
New to collections? Collections
are somewhat similar to arrays,
but in an objectified way. You
can use simple methods like
Add, Remove and Clear to
manipulate collections and,
unlike arrays, you dont have to
track the size of the structure
yourself. Collections also allow
access using indexes, which
can be either numeric or via key
name. For example, its much
nicer to reference an item as
loData.Columns(Company)
than using loData.Columns[5].
By specifying a key in the Add()
method you can greatly
simplify access to the collection
content. Collections lend themselves well for almost any listbased structure that needs to
be dynamically built and
accessed.
Listing 4: Resizing and positioning of the wwStatusBar is accomplished automatically with its Resize event.
LOCAL lnOldLockScreen
lnOldLockscreen = THISFORM.LockScreen
THISFORM.LockScreen = .T.
THIS.Left = 0
THIS.Width = THIS.Parent.Width
THIS.Top = THIS.Parent.Height - THIS.Height
IF VARTYPE(THIS.Panels) = "O"
THIS.RenderPanels()
ENDIF
THIS.oThumb.Left = this.Width - THIS.oThumb.Width
THIS.oThumb.Top = this.Height - THIS.oThumb.Height
THIS.oShadow.Width = THIS.Width
THISFORM.LockScreen = lnOldLockScreen
code-focus
69
code-focus
70 www.code-magazine.com
look with standard color schemes, however, Transparent will likely work better, or you can explicitly
choose a color for your form that works with any
mode. I suggest you play with the different modes
and the wwStatusBar BackStyle property to see what
works best for you.
Finally, realize that wwStatusBar has a dependency
on the images that are used to render the sizing grips
and separators. Figure 4 shows the wwStatusBar
with the embedded invisible image controls that hold
the 3 required images. I chose to include them in the
LPARAMETERS lnPanel
WAIT WINDOW "Heres my panelclick " + ;
TRANSFORM(lnPanel) + CHR(13) + ;
THIS.Panels(lnPanel).Text
Some limitations
Keep in mind that this is a minimalist implementation that isnt completely event enabled. If you
change certain properties of the wwStatusBar object,
make sure that you always call RenderPanel() or
RenderPanels() to refresh the status bar display properly. RenderPanels() is required anytime the sizes of
panels change.
In design mode the status bar displays as a gray
container and it doesnt automatically resize and
attach itself to the bottom of a form like the ActiveX
control does. The display mode for the control is set
to Opaque by default, which guarantees that the
sizing grips look proper regardless of color scheme or
theme chosen by the user. In fact, most apps I
checked (including IE) do not have the status bar
follow the Windows color scheme. To get the best
against panels
IF loPanel.Left <= nXCoord AND loPanel.Left +
loPanel.Width >= nXCoord
THIS.PanelClick(x)
ENDIF
ENDFOR
Rick Strahl