Visual FoxPro To Visual Basic NET 2004 PDF
Visual FoxPro To Visual Basic NET 2004 PDF
NET
By Les Pinter
Les Pinter
Copyright
About the Author
Acknowledgments
We Want to Hear from You!
Introduction
Command Syntax
Flow of Control
Events
Compiler Directives
Data
Data Binding
Summary
Chapter 2.
Summary
Chapter 3.
Why Three-Tier?
What's Next?
Chapter 4.
Summary
Chapter 5.
Chapter 6.
Data Access
Database Containers
Summary
Chapter 7.
XML
Hierarchical XML
Summary
Chapter 8.
Screen Design
Creating Menus
Subclassing Controls
Data Binding
Tools
Summary
Chapter 9.
Summary
Chapter 10.
Reporting
Summary
Index
Copyright
Copyright © 2004 by Sams Publishing All rights reserved. No part of this
book shall be reproduced, stored in a retrieval system, or transmitted by
any means, electronic, mechanical, photocopying, recording, or
otherwise, without written permission from the publisher. No patent
liability is assumed with respect to the use of the information contained
herein. Although every precaution has been taken in the preparation of
this book, the publisher and author assume no responsibility for errors or
omissions. Nor is any liability assumed for damages resulting from the
use of the information contained herein.
07 06 05 04 4 3 2 1
Trademarks
All terms mentioned in this book that are known to be trademarks or
service marks have been appropriately capitalized. Sams Publishing
cannot attest to the accuracy of this information. Use of a term in this
book should not be regarded as affecting the validity of any trademark or
service mark.
Credits
Dedication
This book is dedicated to the memory of my son, John Sebastian Pinter,
who began the project of writing this book with me two years ago. It
would have been a far better book, and this a far better world, if he were
still alive. His share of the proceeds from the sale of this book will go to
the Bay Area Outreach and Recreation Program (www.BORP.org) where
he served as president, and where he developed so many of the qualities
that made him a hero to hundreds of Bay Area children with physical
challenges.
About the Author
Les Pinter is the author of six books and more than 280 articles on
database development. He received a master's degree from Rice
University in Houston, Texas, and finished the coursework for both an
MBA and a Ph.D. from the University of Houston. He joined with Mike
Griffin and Bill Radding in 1980 to market the Magic Wand, the fourth
word processor ever written for the CP/M operating system, and sold the
source code to a 23-year-old Bill Gates, who used it to build Microsoft
Word a year later.
He moved into the database field with dBASE II and dBASE III, then
migrated to FoxBASE during a project at LucasFilm's Skywalker Ranch in
1987. He published the Pinter FoxPro Letter for 10 years in the United
States and for four years (together with Dmitry Artemov and Igor
Medvedev) in Russia.
Les
We Want to Hear from You!
As the reader of this book, you are our most important critic and
commentator. We value your opinion and want to know what we're doing
right, what we could do better, what areas you'd like to see us publish in,
and any other words of wisdom you're willing to pass our way.
Please note that I cannot help you with technical problems related to the
topic of this book. We do have a User Services group, however, where I
will forward specific technical questions related to the book.
When you write, please be sure to include this book's title and author as
well as your name, email address, and phone number. I will carefully
review your comments and share them with the author and editors who
worked on the book.
Email: [email protected]
Michael Stephens
Associate Publisher
Mail: Sams Publishing
800 East 96th Street
Indianapolis, IN 46240 USA
For more information about this book or another Sams Publishing title,
visit our Web site at www.samspublishing.com. Type the ISBN (excluding
hyphens) or the title of a book in the Search field to find the page you're
looking for.
Introduction
About This Book
Database development is fun, the pay is great, and you get to be a hero.
We even get more credit than we deserve. For some reason, if a guy
comes over and fixes your toilet, he's just a plumber; but when your
cousin Danny, a 14-year-old with a bad complexion, gets an Internet
connection working again, they call him a computer genius. Doesn't
make much sense to me, but take advantage of it. What we do is cool.
If you think you missed the boat and lost your chance to prepare for a
good career, you're dead wrong. I first saw FoxPro's predecessor,
FoxBASE, in 1986, when I was 38 years old. That's when my database
career started (unless you count a dozen years with COBOL, which I try
to forget). So if you're 24 and think you're too old to start a career, don't
make me laugh. (You might be too young, though. Sow those wild oats;
then come see me when you're ready to get serious about your future.)
Go through this book and learn how the code works. Make the
application work, understand what it's doing, and how it does it. When
you're done, you'll have a real careerthe kind others dream of.
You can travel all around this little planet, meet other programmers, share
your successes, pass on your discoveries, and generally have a
wonderful time with this career. The languages change every few years,
so you won't be bored. And you'll never, ever master database
programming, because it's endless. There's always something more to
learn, some new challenge to solve, and solutions to be found for your
colleagues who think something can't be done. You can push the limits
as far as you want. Database development can become the foundation
for a successful life.
How This Book Is Organized
This book begins with a review of the major differences between Visual
FoxPro and Visual Basic .NET. It shows examples of object-oriented
programming in both languages by demonstrating a class-based FoxPro
example first, then providing the equivalent in Visual Basic .NET. Finally,
it covers specific topics, allowing you to compare the two languages in
much more detail. Go to www.samspublishing.com to download the code
examples in this book. Go to www.samspublishing.com to download the
code examples in this book.
What's Not Included
A number of topics are not included in this book. I was unable to include
a detailed treatment of thin client development and the construction of
installers, due to page count limitations. I excluded the treatment of the
Visual FoxPro Toolkit for .NET written by Kamal Patel
(www.KamalPatel.net) for the same reason that I excluded the
VisualBasic Namespace; because, the whole point of using .NET is to
discover its capabilities and use them, rather than depending on legacy
features. I've left out remoting and reflection because they are advanced
topics that are not required to build many basic database applications.
With Visual Basic .NET, you can do things that you can't do with FoxPro.
However, if you start at the beginning and work through the chapters,
writing and testing the code as you go, by the time you finish the book
you'll be able to build the same kind of database applications in Visual
Basic that you've built in FoxPro. This should get you started on the rest
of your career.
An Offer You Can't Refuse
To all readers wherever you live, I'll make you an offer you can't refuse:
My email address is [email protected]. If you have any problems with
this book, or can't get something to work, or just need a little
encouragement, send me an email. When I'm gone, my life will be
measured by the number of people whose lives I've affected. So I'd
consider it an honor to hear from you. And if you need assistance
migrating to .NET, give me a call. My entire consulting practice is focused
on migrating Visual FoxPro applications to .NET, and I'd be happy to
share the experience with you.
Les Pinter
Chapter 1. Differences Between Visual
FoxPro and Visual Basic .NET
IN THIS CHAPTER
Command Syntax
Flow of Control
Events
Compiler Directives
Data
Data Binding
Where's the command window? And when you find it, what is it for? In
Visual FoxPro, you can practically dispense with the menu and just type
commands in order to do your work. For example, instead of selecting
File, Open, Project from the menu, you can type MODIFY PROJECT (or
MODI PROJ as most FoxPro commands can be abbreviated to four
characters) and the project name, or just let the list of matching files pop
up and press the spacebar to pick the first one. In Visual Basic, although
there is a command window, the list of commands that you can type is
pretty short.
And while I'm at it, where have all the functions gone? Recent versions of
FoxPro (and of Visual Basic, for that matter) have around two thousand
commands and functions. Visual Basic .NET has only a couple of
hundred. Where did they go?
Most methods are only exposed when you instantiate an object based on
a particular class. For example, TableUpdate() in FoxPro is like a
dataset's AcceptChanges() method. But to get to the point where you can
use it, you would have to create a dataset, which requires creating a new
connection, opening it, creating a new dataadapter, using its Fill method
to create a dataset, displaying the dataset in a table to allow the user to
make changes, then executing the dataset's AcceptChanges() method to
save the changes, like this:
Imports System.Data.SQLClient
Public ds as Dataset
Cn.open()
ds = New DataAdapter
Da.fill(ds)
DataGrid1.DataSource = ds.Tables(0)
...
Ds.AcceptChanges()
...
' Code to save changes back to data store
BROWSE
TableUpdate(.T.)
Some classes are directly accessible, without requiring that you first
instantiate an object based on them. For example, type "Strings.", and as
soon as you type the period, a list of dozens of familiar functions
appears, again thanks to IntelliSense. Similarly, typing "Math." Displays
most of the math functions you know and love in FoxPro. So as long as
you know to start by typing the name of the Namespace that contains the
functions you need, they're only a word away.
FoxPro FUNCTION and PROCEDURE declarations are as simple as can
be. In Visual Basic .NET, Sub and Function declarations seemed to
include keywords of endless variety: Overrideable, Shadow,
Protected, MustOverridewhat did these mean? And did I really have
to know about all of them and use all of them in my code? And variable
declarations in Visual Basic .NET have a different but equally perplexing
variety of options. Do I need to use some of these options? All of them?
In most cases, you can ignore many of the powerful but irrelevant
features in Visual Basic .NET and use only a subset that's just right for
database development. In saying this, I know that I run the risk of
incurring the territorial wrath of some guy who's just written an article
about how critically important it is to use overloading in all of your
applicationsbecause if I'm right, he's just wasted your time and a tree.
(There's a lot of money to be made by saying that things are harder than
they really are, because if you're a consultant, and if you know about it
and they don't and if they really, really need it, they had better hire you.)
But I'm not afraid of controversy, as you may know. Someone has to point
out that the emperor's naked.
FoxPro can create .app and .exe files as well as .dlls. However, the
.app and .exe files contain tokenized FoxPro, not machine code. The
.app files can only be called by other FoxPro programs. The .exe files
are .app files with a loader that Windows can use to run the
encapsulated .app. DLLs in FoxPro are pretty much used only in XML
Web Services. So the generation of a DLL for each project in .NET looks
strange to FoxPro developers. FoxPro apps don't have to be compiled
into an executable unless they're going to be used with the FoxPro
runtime.
Many of the differences between the two languages stem from the fact
that so many more output options are available in Visual Basic .NET. In
the following sections, we'll look at the main differences in the
components of the respective IDEs one by one.
FoxPro's command window saves prior commands. You can use the
UpArrow or PgUp keys to find and execute previously entered
commands, or use the keys to cut and paste code into your programs.
Visual Basic .NET toggles between Mark and Immediate mode. In Visual
Basic .NET's Immediate mode, the up/down arrow keys cycle through
recent commands; in Mark mode (which is like the FoxPro variant), you
can cut and paste. You can use the shortcuts Ctrl+Alt+I and Ctrl+Shift+M
to switch between Mark mode and Immediate mode.
The Toolbox
In FoxPro you add controls to your forms using the Form Controls
toolbar, which can display either the FoxPro base control classes, your
current selection of registered OCXs and DLLs, or any combination of
your own class libraries and the excellent classes that ship with FoxPro.
Starting with FoxPro 7 you can also drag and drop classes from the
Component Gallery. Beginning with FoxPro 8, you have the Toolbox, with
tabs for the Component Gallery and a customizable toolbox where you
can store your own classes.
The Toolbox is the only way to drag and drop controls onto forms in
Visual Basic .NET. In that sense, it replaces FoxPro's Form Controls
toolbar. But it does much more. The FoxPro and Visual Basic .NET
toolboxes are very similar. Visual FoxPro's Toolbox has bars for text
scraps (it's initially seeded with a bug report template and a commented
program header, but you can add entries by highlighting and dragging
them to the Toolbox). The Visual Basic .NET equivalent is the Clipboard,
to which code fragments are automatically added whenever you highlight
code or text and press Ctrl+C. Figure 1.1 shows the Visual Basic .NET
Toolbox.
When the Visual FoxPro Screen Designer is open, the Toolbox entries for
the FoxPro Base Classes, the Foundation Classes, and My Base
Classes permit you to drag and drop screen design elements from the
toolbox instead of dragging them from the Form Controls toolbar. Any
ActiveX controls that you've registered in the Tools, Options, Controls
dialog page will appear here as well, so you don't have to switch between
your class library toolbar and the ActiveX Controls toolbar. And you can
also use the Form Controls toolbar and the Toolbox at the same time.
In Visual Basic .NET, you can build your own user controls, just as you
can build your own classes in FoxPro and then add them to the User
Controls toolbar. In Chapter 2, "Building Simple Applications in Visual
FoxPro and Visual Basic .NET," you'll learn how to add a Windows
Control Library to your project, after which any user controls you add to it
will automatically be displayed in the User Controls toolbar. As in FoxPro,
you use these controls instead of the standard Visual Basic controls on
your forms. And because all of your user controls show up here,
regardless of which Windows Control Library they came from, you don't
have to switch between VCXs as you do in FoxPro.
TIP
Tab Ordering
In Visual Basic .Net, interactive tab ordering is the only way that's
available. To start the process, select Tab Ordering from the View menu
pad. When you're done, select Tab Ordering from the menu pad again;
clicking somewhere on the form won't do a thing.
Visual Basic .NET adds one more layer on top of the projects layer: the
solution. I think the marketing guys had a hand in this, because where I
come from, it's not a solution until it solves something. But I suppose they
had to call it something. And I like what it does.
Figure 1.2 shows a Visual Basic. NET solution containing a Web service
and a Windows form application that uses the Web service. The two
FoxPro projects in Figure 1.3 do the same thing, but you just have to
remember that they're related to one another. In both cases the Web
service and the application that uses it are in different directories.
In FoxPro, although you can compile to APP, DLL, or EXE format, you
generally build EXE files. APP files can only be called from another
FoxPro application and DLLs require some additional work that generally
isn't worth the bother. In Visual Basic .NET (and in earlier versions of
Visual Basic), it's common practice to build components as Dynamic Link
Libraries (DLLs). Typically, at least one of the projects in the solution
compiles to EXE format. DLLs built from other projects in the
solutionclass libraries, user controls, and the likeare used as components
of the executable.
When you build a FoxPro project, you can build an APP, an EXE, a
single-threaded DLL, or a multithreaded DLL. APPs can be called by the
FoxPro runtime, but I almost always use EXEs. I've only used DLLs for
Web services and seldom even then. You can either click on the Build
button, press Alt+D, or type BUILD EXE Name FROM Name (BUILD DLL
Name FROM Name, or BUILD MTDLL Name FROM Name for
multithreaded DLLs).
In Visual Basic .NET, DLLs are used as you might use class libraries in
FoxPro. For example, you can build an inheritable form (a Form Template
class in FoxPro) as a DLL and inherit from it in another form, after you
include the DLL as a reference in your project. When you build a Class
Library project in Visual Basic .NET (by right-clicking on the project name
in the Solution Explorer, selecting Properties, and setting the Output
option to Class Library), the project compiles to a DLL.
NOTE
The name Dynamic Link Library (DLL) derives from the fact
that they are linked when called (dynamically) rather than
when the executable is created. So, you can run them from
within other programs. They are said to run in-process as
opposed to out-of-process in the non-FoxPro world, where
components are compiled as DLLs; for us, the distinction is
meaningless.
When you build a project, the type of project you select determines which
references to .NET class library components will be automatically
included. You can then add any other references (DLLs or namespaces)
that you need. For example, if you want to inherit from an inheritable form
that you had built previously, you would add a reference to the inheritable
form DLL in your project before trying to add the inherited form. In that
way, the project knows where to look for and display any available
inheritable forms.
The nearest FoxPro equivalent would be adding class libraries to a
project. (The other case where you add references, when you add
ActiveX controls to forms, involves using the Tools, Options, Controls
page to add ActiveX controls to the Selected list. This causes them to be
displayed in the Form Controls toolbar ActiveX menu. However, ActiveX
components don't explicitly appear in the Project Manager's file list.)
In Visual Basic .NET you first designate the Startup project because a
solution may contain several projects. You can see this if you build my
favorite .NET walkthrough, Creating a Distributed Application. (This
walkthrough can be found in the Visual Studio .NET, Samples and
Walkthroughs Help selection. In it, you build a solution containing a Web
service, then add a Windows client application, and finally add a Web
client application.) You can designate any of the three as the startup
project. If you choose the Web service, a test page is automatically built
and presented to you to test those of your Web services that are testable
with keyboard input (that is, those that don't have a diffgram [a dataset
containing changes made to another dataset] or other type of dataset as
the input parameter). Change the Startup Project selection in the Solution
Explorer and you can test either of the other projects. It beats fumbling
around trying to remember the name of your latest FoxPro Web Service
directory.
The code window for a Windows Forms project is shown in Figure 1.4.
Unlike FoxPro, which puts each code snippet in a separate window,
they're concatenated in Visual Basic. The Text Editor toolbar contains
elements for indenting text, setting and jumping to bookmarks, and
displaying object and parameter information.
To jump to a code snippet that you haven't yet coded in a Visual Basic
.NET form, you select the object (for example, the form or a button or list
box) from the drop-down list at the upper left of the code window screen,
and then select the corresponding event or method from the drop-down
list at the upper right of the screen. Just as in FoxPro, the names of
snippets containing code appear in boldface. In FoxPro you can open the
Properties window and pick the Methods tab, and then double-click on an
event or method name to open its code snippet; you can't do that in
Visual Basic .NET because events and methods don't appear in the
Properties window page. It makes more sense in Visual Basic .NET, but
the convenience of FoxPro is nice, too.
Bookmarks and a Task List are both available in both Visual FoxPro and
Visual Basic, and perform very similar functions. To create a bookmark in
FoxPro, Shift+Double-click or use Alt+Shift+F2. In Visual Basic .NET, use
Ctrl+K, which is a toggle. You can also click on the Toggle Bookmark
button (the little blue flag) in the Text Editor toolbar. In fact, there are four
command buttons in the Text Editor toolbar for managing all aspects of
bookmarks; in FoxPro, you have to use the context menu.
Server=(local);Database=Northwind;UID=sa;PWD=;
Thus you can open a database and then view, edit, or add data without
opening up the SQL Server Enterprise Manager or the Query Analyzer.
You can also create tables, indexes, and relations. Still, the connection to
your tables seems a little removed compared to FoxPro. And there's a
slight delay when you access any data table. However, you can do all of
the things that you can do with FoxPro tables, albeit in more roundabout
ways.
In Visual Basic .NET, using local data sources (like DBFs or Access MDB
files) implies exactly the same degree of difficulty as does the use of SQL
Server. As a programmer, you save your client no programming costs if
you use local tables, as would have been the case with FoxPro.
You can't do that in Visual Basic; you can only execute an executable.
But it's easy. Press F5 and the designated Startup project runs. To test a
form, you'll need to add a couple of lines of code in your main form menu
to instantiate the form class as an object and call its Show() method, like
this:
Frm.Show()
That's two lines of code instead of one, but it's almost as easy as DO
FORM CustForm.
Both FoxPro and Visual Basic .NET have a class browser. If you need to
become familiar with a class in a FoxPro VCX, you can use the Class
Browser to examine its structure (although you can't edit the method
code in the Class Browser). Also, you'll need to use the Open dialog to
locate and open a class. Visual Basic .NET's Class view automatically
shows all of the classes in the current solution in a tree view, and permit
code editing. To jump to the code for a method, double click on its name.
For your own classes, you use the Class Browser; for all other objects,
there's the Object Browser. FoxPro's Object Browser lets you open a type
library from any of the registered classes on your computer and browse
through the class hierarchy, whereas Visual Basic's Object Browser uses
the currently open solution to navigate (although you can browse objects
that aren't in the project as well.). It lists the objects of whichever project
you click on in the Solution Explorer. Visual Basic .NET's Object Browser
not only knows what's in your project, but remembers selected
components from session to session. FoxPro's Object Browser is not
connected to the current project.
In both Visual FoxPro and Visual Basic .NET, properties can be listed
either alphabetically or grouped by categories. These categories are fixed
in FoxPro, whereas in Visual Basic .NET you can add your own by
preceding property procedures with the <Category("XYZ")> attribute
(provided that you've included Imports System.ComponentModel at
the top of your class). Precede your custom categories with an asterisk
(for example, *XYZ) and they'll appear right at the top of the Properties
window.
The most important difference between the two languages in this regard
is that in FoxPro, properties are like public variables of classes and have
immediate visibility in the Class Designer as well as in classes that inherit
from the class. In Visual Basic you need to write a property procedure
(as follows) consisting of about eight lines of code for each and every
property that you want to see in the Properties window.
As soon as you press Enter, the IDE adds six lines of code.
Furthermore, unlike FoxPro, in Visual Basic .NET you only see your
properties in classes that inherit from your classnot in the class itself. So
if you define property procedures in a class, you won't see them in the
Properties window. As soon as you inherit from that class, the inherited
object's Properties window will expose the property.
If you're writing classes in a .prg file and you want Assign and Access
methods for your properties, simply use the name of the property with
_Assign or _Access added to the end of the property name as the
procedure name, and it will indeed fire when the property is assigned a
value or when the value is read, respectively.
I've only had occasion to use Assign and Access methods twice in my
professional career, so I don't think they're very important. Requiring
eight lines of code for every single property in a class on the off chance
that you might want to use Assign and Access methods seems to me
to be a case of the tail wagging the dog. However, you absolutely have to
use property procedures if you want to use properties as you do in
FoxPro.
Shortcut Keys
The IDE is where you spend your day, so it's important to know where
everything is. By this point in your career, you probably don't even need
to look for things. When you want to open up the Properties window, your
hands type Ctrl+W, P without any conscious effort on your part.
You need to develop the equivalent reflexes for Visual Basic .NET. The
task is somewhat complicated by the fact that FoxPro is a command
window-based IDE, so that all of the features that are accessible from the
menu, or from toolbars, are generally accessible as well by typing a
command string.
Table 1.1. Keyboard Shortcuts in Visual Basic .NET and Visual FoxPro
FoxPro can be managed from the Tools, Options dialog. The Tools,
Options page contains a page frame with 14 pages, containing settings
that have huge implications for the way the IDE, and your applications,
work. Some of these pages have equivalents in Visual Basic .NET, but a
few of them don't. For example, the Data and the Remote Data pages,
which handle access to DBFs and to SQL using remote views
respectively, are irrelevant in Visual Basic .NET; the Data page controls
access to DBFs in FoxPro, and in Visual Basic .NET DBFs are just
another OLEDB or ODBC data source. Also, the purpose of Remote
views in FoxPro is to provide access to SQL Server table data in a way
that's very similar to the way we use DBFs.
The Options page I wish Visual Basic .NET had is the Field Mapping
page, which controls which of your classes are used to automatically
populate a form. There is no equivalent feature in Visual Basic .NET. The
Data Form Wizard does some things that are interesting, but I use
inheritance for forms, and the wizard doesn't. It's the greatest
shortcoming of the form design environment.
Visual Basic .NET also has a Tools, Options menu selection. The
resulting dialog uses a tree view for navigation instead of a page frame.
In addition, some of the settings found in FoxPro's Tools, Options dialog
are to be found in the Solution and Project Properties sheets,
respectively. Right-click on either the solution or a project and select
Properties from the resulting context menu.
Table 1.2 shows the location of equivalents to the FoxPro Tools, Options
Page elements in the .NET IDE.
No similar default background exists in Visual Basic .NET. Your first form
is the container for all other forms. You manually drop a MainMenu
control on the form and use Visual Basic .NET's delicious menu designer,
which I would have loved to have in FoxPro.
In FoxPro, I usually put the form that I'm working on at the upper-left
corner of the screen; the Project Manager, Properties window, and the
command window over on the right. I reduce the screen resolution to the
smallest size that doesn't give me a headache, then expand the
Properties sheet to fill almost all of the vertical size of the screen; the
Project Manager and the command window split what's left at the lower-
right corner of the screen. The Toolbox goes somewhere on top of the
properties window whenever I need it.
Data lists excluded tables as well as files that define queries and
views.
Items can be listed in the project file but are marked as excluded, so that
they are not part of the executable and can be modified. For example, by
default DBFs are marked as excluded in the Project Manager. If a DBF
(say a table of States) is included in the executable, it can be read but
can't be modified. This is sometimes useful, but rarely. Items that are in
the current directory but haven't been included in the project don't appear
in the Project Manager files list.
TIP
Finally, if you create an empty project and add your MAIN program, the
BUILD PROJECT command will start by reading all components
referenced in MAIN and then follow the rabbit trail until the project has
been rebuilt.
In Visual Basic .NET, when you create a project, a solution of the same
name is also created, with a hierarchy consisting of a bin and a debug
directory. In the Solution Explorer there's a Show All Files button; when
you click it, all of the files in the application directory are displayed. You
can right-click on them and include them in the build. Once the files are
included, the Properties window gives you several options for the build
actionnone, content, compile, or embedded resource.
All of the code files in a project must be written in the same language.
However, you can build a solution with one Visual Basic (.vb) project
and one C# (.cs) project, and they'll make beautiful music together. This
is, of course, because both have been translated to Intermediate
Language by the time the solution is built, and neither one cares which
boat the other's ancestors arrived on.
Intermediate Language
The FoxPro IDE does something similar. If you DO a PRG inside FoxPro
and if an .fxp file exists with an earlier time stamp, FoxPro automatically
compiles the PRG to produce an FXP. If the FXP is more recent, it's run,
and the PRG is ignored. Of course, we don't send PRGs to our
customers, and the FoxPro runtime can't compile them; but the analogy
is useful.
In general, project elements are either code or data. If they contain code,
their extension indicates which language you chose for your project
(currently .vb, .cs, or .js for Visual Basic, C#, and J#). There are also
a host of extensions that have been created for various components of a
Visual Studio solution or project component. Generally, these are tables
in XML format. In FoxPro we use DBFs with other extensions; the notion
is precisely the same.
Similarly, .xsd (schema) files have an associated .vb or .cs file if they
represent a typed dataset. Typed datasets allow FoxPro developers to do
something near and dear to our heartsuse the Table.Field syntax in
Visual Basic .NET that we have always had in FoxPro. To do so in Visual
Basic .NET, there must be a class with one property procedure for each
field name in the table in question. These classes are automatically
generated by the Generate Dataset phase of the DataAdapter Wizard
dialog.
If you generate a typed dataset (an XML schema file with an .xsd
extension), the .xsd file will have a .vb file behind it with one property
procedure for each column in the table. You have to click the Show All
Files button to see it. Typed datasets are the only way you'll be able to
type employee.lastname in Visual Basic .NET. You don't even want to
know what you have to type to refer to a field without them. I imagine that
FoxPro does something similar internally, although not knowing about it
has never bothered me.
TIP
Other files in the project use XML files to store tables of information
concerning how to manage and build the project. An XML file in its
simplest form describes rows and columns, so it's exactly like a DBF
without the ability to have an index and thus be searched quickly. You
can think of them as dumbed-down DBFs. Generally, if you double-click
on a file with a .vbproj, .sln, .xsd, or other extension, you'll get
XML; and at the lower-left corner of the screen you'll see a Data tab.
Click on it to see a tabular representation of the XML.
DLLs
That's not the world of the rapid prototyper, the world we as database
developers live in. My clients' requirements shift daily, sometimes hourly.
I simply can't send out a memo requesting changes in a DLL, and then
wait a few weeks for them to code, test, and ship back the result for me
to test.
In FoxPro, although you can do this, I've never felt the need to build
components and stitch them together to build the executable. It just never
occurred to meand still doesn't, come to think of it. I don't see changing
our thinking to accept black-box DLLs as a normal component of
application development in FoxPro. But in Visual Basic .NET, classes
compile to DLLsthere is no equivalent to FoxPro's VCX file.
FoxPro uses a current directory concept. At any time you can type CD in
the command window to see "where you are"that is, what the current
directory is. You can also type ? SET("DEFAULT") or ? CURDIR() to
display the current directory in the output window (_Screen). You can
use CD (path) or SET DEFAULT TO (path) to change it. Before
creating a project, you use CD and MD commands to create and navigate
to the folder the project will occupy.
The mere fact that files are located in this directory doesn't make them
part of the project, and in fact a project isn't strictly necessary. FoxPro is
unique in that you can run programs without compiling them. If you type
DO MAIN in the command window, the program will run exactly as if you
had compiled it and run the executable. And even if you do build an
executable, if FoxPro can't find a program, screen, menu, or class file in
the executable, it searches first in the current directory, and then along
any paths specified with a SET PATH TO command (for example, SET
PATH TO Classes;Menus;Programs).
If you type CREATE Table1, a dialog opens to allow you to build a table
schema and store it in a file with a .dbf extension (and optionally an
additional file with the same name and the extension .fpt if your table
schema contains variable-length fields called "memo fields"). Any DBF
that has variable-length fields has an associated memo file with the
extension "FPT". There are other extensions that are used for other
types of files. I'll introduce each of them.
Projects are stored in one of these file pairs with the extensions .pjx
and .pjt respectively. The "PJX" is the "DBF" file, and the "PJT" is
the "FPT" file. It shouldn't surprise you that FoxPro uses a table to store
the project details. Screens, classes, and reports use the same scheme
of pairs of files with extensions that suggest their usage.
Visual Studio uses HTML files for similar purposes. For example, each
page in an ASP.NET application is stored in an HTML file with the
extension "aspx", which is essentially a table containing the descriptive
elements of the page. The code is stored separately in a "code-behind"
file. But if you change the page, you don't have to recompile, because it's
just dataexactly as if it were coming from a DBF.
By default, any files you create for a project will go in the current
directory. When you create a project using the MODIFY PROJECT Name
command, the .pjx and .pjt files are created. Subsequently, as you
add other files like MAIN.PRG, they are put by default into the current
directory.
NOTE
If you create a form (screen), a pair of files with .scx and .sct
extensions are added. Again, if there's no project, FoxPro will compile
and run the SCX file using the DO FORM FormName command. The
SCX/SCT files don't have to be part of a project.
If you create a class library, a VCX/VCT pair is created. It has exactly the
same column layout as an SCX/SCT file pair. (Think about that for a
while. This is the reason that you can select Save As Class in the Form
Designer and then instantiate a form object from the saved form class
using frm = CREATEOBJECT("formclassname") followed by
frm.Show()almost exactly the same syntax used in Visual Basic .NET.)
TIP
If you use a FoxPro project file, when you build the application, any
included PRG files are compiled, and the generated FXP code is added
into the corresponding MAIN.PRG record in the object memo field. If you
called an MPR (menu) program, it is compiled into an MPX file and the
MPX is included in the executable. Any SCX (screen) and VCX (class)
files are compiled and added to the executable.
After the source files have been compiled, you don't need to ship any of
them to your users; the executable contains everything. However, if
there's no project, FoxPro will look for and execute the MPX file if it
exists; if not, it will look for and compile the MPR file, and then run the
resulting MPX file. It won't automatically build an MPR file from an MNX
file, though; you have to do that in the Menu Builder, or let the Project
Manager do it for you at build time.
Similarly, any referenced screen files are called as needed. Class library
files, on the other hand, whether in VCX or as DEFINE CLASS
statements located in PRG files, must be referred to in the code with SET
CLASSLIB TO VCXFileName ADDITIVE and SET PROCEDURE TO
PrgFileName ADDITIVE in order for FoxPro to know how to use their
contents.
Reports are stored in FRX/FRT pairs. It's common practice to exclude
report files from the project and then simply build a list of available FRX
files for the user to select. In this way, customized reporting is easy to
support.
Unlike Visual FoxPro, Visual Basic puts your project in the directory you
name when you create the project. By default the project directories you
create are created under a default projects folder, which you can and
should change when you start using Visual Studio.
For each project, .NET creates a project folder using the project name
you supply, within your default projects folder. It also automatically
creates a solution (with the extension .sln) with the same name as the
project. A solution is a grouping of related items such as a form and the
routine that returns data to it, or a Web page and a Web service. There
can be any number of projects in a solution, but only one of them is the
startup project that runs if you press F5.
FoxPro doesn't have this additional layer. I wish it did. I have to write
down which Web service goes with which applications on little sticky
notes. A little bookkeeping help would be nice.
Project Information
For that reason, you should take a few minutes to go over Table 1.3 and
familiarize yourself with some of the principal similarities and differences
in command syntax.
Continuation
_ (underscore) ; (semicolon)
character
Data type
Deprecated in Visual Basic {} (curly braces) around dates
indicators
Attributes of
<attr("value")> No equivalent
functions
Array Dim X(2) is 3 long; X(0) is first one; all Dim X(2) is 2 long; X(1) is first one;
dimensioning elements same type elements can be of different types
Default prefix
WITH/ENDWITH WITH/ENDWITH
block
Used to call as yet unnamed functions in Don't have 'em, don't need 'em
Events other modules
Object creation
from a COM CreateObject("Word.Application")
CreateObject("Word.Application") or GetObject("Word.Application")
object
Reference to
ME THIS, THISFORM
container
If statement
IF Expr Then; End If IF Expr; ENDIF
syntax
This is not meant to be an exhaustive list; we'll save that for Appendix A
at the end of the book, where you can find a concordance of Visual
FoxPro and Visual Basic equivalents.
Flow of Control
Branching, looping, and conditional execution are the fundamental
building blocks of programming. Almost every one of the mechanisms
listed in Table 1.4 is slightly syntactically different in the two languages,
although clearly, you can accomplish the same things in either language.
Name() or CALL
Call a procedure Prog() or DO Prog
Name
DO/Loop,
DO/ENDDO, FOR
For/Next, For
Looping [EACH]/ENDFOR|NEXT,
Each/Next, SCAN/ENDSCAN
While/End While
Choose,
IF/Then/Else,
Conditional branching IF/ELSE/ENDIF, DO CASE
Select Case,
Switch
Function, Sub,
Procedure definitions Property Function, Procedure
procedure
Following the table, I've made a few notes on how each construct works.
IIF and SWITCH are included here as well because they replace what
used to be four or five lines of IF/ENDIF or CASE/ENDCASE code.
In Visual Basic .NET the syntax is exactly the same, except that DO
ProcName() is not supported.
String handling
FoxPro:
Name = "Fred"
Age = 32
TEXT TO X
ENDTEXT
? X
VB:
DO WHILE .T.
oPrinter.Test
IF oPrinter.ErrorOccurred or oPrinter.PrintedOk
EXIT
ENDIF
ENDDO
FOR I = 1 TO N
- do something -
ENDFOR | NEXT
or
- do something -
ENDFOR | NEXT
SCAN
- do something -
ENDSCAN
The Visual Basic equivalent is to use a FOR EACH row IN table loop:
Dim dr as datarow
...
End For
FOR I = 1 TO N
- do something -
NEXT I
However, DO...LOOP has four variants. The first two check the
expression at the beginning of the loop, whereas the last two check the
expression at the end of the first loop:
Do While:
Do While condition
statements
Loop
Do Until:
Do Until condition
statements
Loop
Do...Loop While:
Do
statements
Do...Loop Until:
Do
statements
While condition
statements
Wend
In Visual FoxPro you can use either of these two mechanisms to execute
only a certain block of code:
IF Expr
- Execute if true -
ELSE
ENDIF
or
DO CASE
CASE Expr1
CASE Expr2
ENDCASE
Choose returns the numbered entry from a list of choices, for example:
Choose is a primitive construct from years ago, and will seldom if ever be
the best choice. It has been deprecated.
If Expr Then...Else...End If
Bonus = 1000000.00
Else
Bonus = 25.00
End If
Note that a single line implementation is permitted. If it improves
readability, use it. When I'm writing code for publication I use the single-
line implementation. You can also include several statements separated
by colons, but it gets hard to read pretty fast.
Select Case
Case "CA"
Governor = "Arnold"
Case Else
Switch(VisualBasic Namespace)
Return Microsoft.VisualBasic.Switch( _
Note that the Switch function name is not unique in the .NET
namespaces and so must be qualified. This version is from the Visual
Basic compatibility namespace provided for people who are inordinately
fond of Visual Basic6. I'd be sparing about using this function; it looks a
little dated.
Data Types
Data type declarations in FoxPro take two forms: fields in tables and
memory variable creation. Scope declarations can be used to declare
data types in FoxPro, for example:
MyVar = "Oops!"
Table 1.5 compares the data types available in Visual Basic to the
FoxPro data types and to FoxPro DBF Field types.
Table 1.5. Data Types in Visual Basic .NET and Visual FoxPro
Visual FoxPro
Data Type Visual Basic Visual FoxPro Field
memvar
General(OLE
Object Object Object,Variant
objects)
In Visual FoxPro, some confusion results from the fact that PUBLIC,
PRIVATE, and LOCAL refer to variable scope, whereas PROTECTED and
HIDDEN refer to property and method visibility, and PROTECTED will
always be part of a FUNCTION or PROCEDURE declaration in a class
definition; no confusion there. But PUBLIC can refer to either a variable
or to a property or method of a class.
Name=[Les]
THISFORM.Caption = Name
the form's caption will change, but as soon as the form closes, the
variable Name disappears. Technically, it is said to go out of scope.
LOCAL means that a variable exists only within the procedure in which it's
named, whereas PRIVATE means that it exists in the procedure in which
it's created as well as in any procedure called by that procedure.
? "Hi"
ENDFOR
X = 3
FirstName = []
LastName = []
ENDDEFINE
FoxPro generally declares all variables as variants, which means you can
assign any value to them. In fact, you can change their data type in the
middle of the program�usually inadvertently and with confusing results.
It's not a good idea, but it's possible. However, you can declare types
using the PUBLIC, PRIVATE, and LOCAL declarations:
PUBLIC I AS Integer
You can also declare objects based on classes in your class libraries, or
from the FoxPro base classes:
Visual Basic .NET is rendered more confusing because there are scope
declarations for functions and procedures as well as for variables. We'll
look at these in great detail shortly.
In Visual Basic, variables are objects. In fact, you can't use variables
unless you first declare them with a statement that specifies which object
they're derived from. Best practices dictate that you use Option
Strict On and Option Implicit Off, so that the compiler will stop
you from trying to assign an integer to a string variable. This can catch a
certain type of programmer error that can be hard to diagnose.
This is the reason that many functions in FoxPro are methods of objects
in Visual Basic. For example, in Visual FoxPro you use the
UPPER(StringVar) function to convert a string to uppercase. In Visual
Basic .NET, strings are objects, with dozens of built-in methods. The
string object's methods include a ToUpper method. So the Visual Basic
.NET equivalent of UPPER(StringVar) is StringVar.ToUpper. In a
way it's better: Type a string variable's name, press the period key, and
IntelliSense shows you all of the object's methods. In FoxPro, you'd be
searching Help about now.
Variables are created from the named type using the DIM statement,
which has the following syntax:
If any of the first nine declaratives are used, Dim can be omitted. An
explanation of each of these options follows:
Type�Any of the data types that Visual Basic supports. The list is
slightly longer than that of FoxPro, but you'll always know what type
to use. String is character, int is integer, double is numeric. Believe
me; knowing the corresponding names of data types is the least of
your problems.
Any of these attributes can be used with the DIM statement to allocate
memory for variables. DIM comes from DIMENSION, which was the only
way to allocate memory for variables in the early version of BASIC and
was never replaced by ALLOC as it should have been. The DIM keyword
is optional if any of these others are used, so it's often left out. If only DIM
is used, it defaults to Private. That's part of the source of the confusion.
Just think of Public, Protected, and so on as alternate ways of saying
Dim. If you want to use variables in classes as we use them in FoxPro,
you can generally use Public or Friend. Inside a procedure, just use
Dim.
Enumerations
Visual Basic is rife with names of possible choices for everything. For
example, if you type
MsgBox ("Hi",
If MsgBox(
"Print", _
MsgBoxStyle.Question _
and MsgBoxStyle.YesNo _
and MsgBoxStyle.DefaultButton1) _
= MsgBoxStyle.Yes
than it is to read
as we do in FoxPro.
In Visual Basic, there are hundreds and hundreds of them. They are one
of the reasons why IntelliSense is a part of Visual Basic. IntelliSense is
nice to have in FoxPro; Visual Basic would be dead without IntelliSense.
If you want to provide names for your lists of numeric values that are
assignable in code, you can use an enum to provide IntelliSense support.
After you define your enumeration, the editor will pick it up and use it the
way it uses the enumerations that are built into the language. Enums can
be integers (one, two, or four bytes).
Enum SecurityLevel
IllegalEntry = -1
MinimumSecurity = 0
MaximumSecurity = 1
End Enum
Arrays
Dim x(5) as Integer ' the array will have six elements
Arrays in FoxPro can contain elements of different data types; that's why
SCATTER TO ARRAY (name) works. You use DIMENSION or DECLARE
to create an array and then assign values to its elements. A subsequent
DIMENSION or DECLARE statement can increase or decrease the size of
the array. Arrays, like variables, can be PUBLIC, PRIVATE, or LOCAL.
Arrays in Visual FoxPro are either one- or two-dimensional. The size of
an array named X is given by ALEN(X) or ALEN(X,0); the number of
rows in a two-dimensional array is given by ALEN(X,1); and the number
of columns by ALEN(X,2).
In Visual Basic, all array elements must be of the same type. REDIM
resizes an array, and REDIM PRESERVE does so without erasing the
contents. We don't have PRESERVE in FoxPro because array contents
are never removed if an array is redimensioned; if you want to do so, you
RELEASE and DIMENSION the array anew.
DIMENSION X(1)
DIMENSION X(4)
expands the array from one element to four. The contents of the first
element of the array are not changed. The VB equivalent might be this:
DIM X() As String = { "Hi there}
Table 1.6 summarizes the handling of arrays in Visual FoxPro and Visual
Basic.
X = CREATEOBJECT("Collection")
x.Add(_Screen,"one")
You can also set properties or call methods on the object as a collection
member, for example:
X("one").WindowState = 1
x.Remove("key") or x.Remove("key") or
Remove an item x.Remove(n) x.Remove(n)
Fields
Just remember that the field is the default. If both exist, the current table's
field is assumed to be what you want, unless you preface the memvar
with "m.". It's actually a common hard-to-find bug. The best fix for this is
to preface the field name with its table name and a period, for example,
Customers.Address, and to preface memvars with "m.". Alternatively,
you can preface all memvars with an additional m. It seems like overkill,
but FoxPro developers are used to it.
Visual Basic .NET doesn't have fields. Well, it does, but field in Visual
Basic .NET means what we'd call a local variable in a class in FoxPro.
But as you'll see, typed datasets act pretty much like FoxPro tables do in
this regard. A typed dataset is a Visual Basic program, usually (make that
always) generated by a program, that adds one property procedure for
each field in a table. That is, when you create a dataset (an .xsd file,
which is actually an XML file), if you ask the wizard to create a typed
dataset, it will generate the code for you.
There are third-party tools available that will do this job for you, and I
suspect that Whidbey (the next version of Visual Basic .NET) will do so
as well. RapTier from SharpPower.com builds a robust data tier. Visible
Developer and DeKlarit do even more. All of these are better than typing
these things in yourself, or using the one that's automatically generated
by the IDE.
Typed datasets allow you to do data binding using a syntax that looks just
like FoxPro's; for example, you fill in tablename.fieldname in the
DataBindings - Text property (the equivalent of the FoxPro
controlsource property). However, data binding goes only as far as
the dataset, which is a local cursor. It doesn't write the data back to the
source table, wherever that is. For that you need a command object, as
you'll see shortly.
Functions and Subroutines (Procedures)
In both FoxPro and Visual Basic .NET, code is divided into modules
called functions and procedures (subroutines in Visual Basic .NET).
These can be modules, or they can be methods in classes. You use
Name() to call a function or procedure, and obj.Name() to call a
method of a class.
When you define a class, whether in a PRG or in a VCX, you can add
methods, which either return a value or don't. (In Visual FoxPro there is
no distinction between function methods and procedure methods.)
When you add a method to either a Visual Class Library (VCX) or a class
in a PRG, you designate it as Public, Protected, or Hidden. This
determines the method's visibility when instantiated. If you define the
class and its methods in a PRG, that is, a procedure library, you precede
method names with PROTECTED or HIDDEN (the unstated default is
PUBLIC):
Function Declarations
Visual Basic .NET is very particular about this. Functions must return a
value, and it must be of the type specified in the RETURN statement. To
make matters worse, someone in Redmond (and I think I know who it
was) once decided that assigning the function's value to its name was a
good way to hide the RETURN statement, or save a line of code, or who
knows what. So you see code like this:
DollarsToCharacters = CSTR(inputval,10,2)
END FUNCTION
Functions and Procedures in FoxPro
Typically this is done early in the MAIN program, after which the
contained functions and procedures can be referred to as if they were
FoxPro functions, that is, with no object name preceding them.
Functions are declared with the FUNCTION <name> ... ENDFUNC and
PROCEDURE <name>... ENDPROC blocks. Methods in visual class
libraries (VCX files) are not characterized as either functions or
procedures, because fundamentally, FoxPro doesn't care whether you
return a value or not.
PARAMETERS InputVal
RETURN STR(InputVal,10,2)
ENDFUNC
or
RETURN STR(InputVal,10,2)
ENDFUNC
Brace yourself for the Visual Basic .NET equivalent. First, I'll show you
the formal definitions. (Note that the only difference between functions
and subroutines is the As type clause in the function declaration, which
indicates what data type will be returned.)
[ statements ]
[ Exit Sub ]
[ statements ]
End Sub
[ statements ]
[ Exit Function ]
[ statements ]
End Function
Overloads This and several other functions with the same but
different parameter lists (called signatures in .NET) will be defined in
the class or module, and all of them will do the same thing using
different parameters. This is good if you're building a compiler, but is
not important for database applications.
Shared This can be called either directly from the class (without
instantiating an object from the class) or by instantiating an object
based on the class.
The second qualifier determine visibility, that is, where the function or
procedure can be seen.
In .NET, the New() function is the constructor, and it can also be used to
pass parameters while creating an instance of a class. However, it's
common in the .NET Namespaces to have several overloads of the New
method, so that you can instantiate an object and pass it several types of
parameters. The only difference between different overloads is their
parameter lists; for example, when creating a DataAdapter object, you
can either pass it a SQL select statement and a connection string, like
this:
, PWD=sa;")
Cn.Open()
So even if you don't ever overload any of your own methods, you'll use
overloaded .NET methods every day. When you do so, you'll need to add
the statement
MyBase.New()
as the first line of your overloaded method, to call the object's original
constructor.
Implementing Interfaces
End Interface
End Class
Implements Class1.Foo
Get
End Get
End Set
End Property
End Sub
End Function
End Class
It's pretty hard to screw this up if you use these declarations. And as an
added bonus, if the Interface definition in Class1 is changed, its
implementation in Class2 produces a compiler error. On the other hand,
you have to be pretty sure what you want Class1 to do before you
define the Foo interface.
Static means that the variable continues to exist after the routine
in which it was created is over. In that sense it's like FoxPro's
PUBLIC declaration. Note that you can't specify Static with either
Shared or Shadows.
It's very, very simple. In FoxPro, put the initial assignment of variables in
MAIN to default them to PUBLIC scope, and then declare variables as
PRIVATE in all called routines that themselves might call other routines
that might need the private valuesfor example, a screen that set up titles
for a report. Use LOCAL for all variables that are definitely used only in a
single routine (for example, loop counters). You can completely ignore
method scope and make them all PUBLIC, the default. And remember
that in general, variables that are used only by methods of a class should
be public properties of that class, not PUBLIC variables in MAIN.
In Visual Basic, you can do the same thing. The greater variety of
declarations in Visual Basic .NET implies that it's desirable to take
advantage of every one of the options. However, as database
developers, we don't need most of them.
Make all methods of your classes Shared Public, and ignore all of the
nuances. Don't use Overrides and Overloads. And if you find yourself
using Shadows a lot, consider redesigning your base class or writing two
different classes.
The fact that you don't need to create multiple overloads of subs and
functions doesn't mean, however, that you won't see them every day of
your programming life. A great many of the .NET classes have
overloaded methods. Let's say you want to create a connection to SQL
Server. If you do this
Dim cn As SQLClient.SQLConnection(
when you press the left parenthesis, nothing happens. However, if you
type this:
you will be presented with a little tooltip box that informs you that you're
about to use overload 2 (of 2 available overloads), which accepts a
connection string. Multiple overloads of New() methods for .NET classes
are ubiquitous, and rather helpful. I still don't think you need to overload
your own methods, but you'll use overloaded methods every day.
Classes, Namespaces, Properties, and Forms
Many FoxPro developers began with version 1, which didn't have object
orientation. We built forms that did what we wanted. If we needed a
similar form, we cloned and modified the code. There was a template
capability that allowed us to migrate features to the template and then
stamp out similar forms like a cookie-cutter. But subsequent design
changes could be painful.
When Visual FoxPro came out, for the first time we had the ability to write
generic form class code and inherit forms from it. My own approach was
to build a basic form and then slowly move code out of it into an
underlying class, changing form references to a specific table name to
the contents of a property, so if I changed
SELECT EMPLOYEES
to
SELECT ( THISFORM.MainTable )
And set the same dozen properties, and the form was up and running in
minutes. I was in heaven! My customers got more software for less
money, and I could win every single bid I made.
However, classes don't work exactly the same way. For one thing,
FoxPro form classes are usually stored in a table with the extension
.vcx (and an associated memo file with the same name and a .vct
extension). Visual Basic .NET forms are source code files that don't look
any different from program files. They have the extension .vb.
However, the general idea is the same: Classes have properties and
methods. You instantiate an object based on the class, assign values to
its public properties, and call its public methods. It uses its private
properties as variables, and calls private methods to do its work. In this
regard, FoxPro and Visual Basic .NET are identical.
However, the devil is in the details. And there are lots of details that differ.
For one thing, FoxPro classes are stored either as VCX files or PRG files.
If they're stored as VCX files, you use the Class Designer to add
properties and methods, which can be either Public, Private, or
Protected. If they're defined in a PRG, the file starts with this line of
code
ENDDEFINE
Properties are created simply by assigning them values, one per line,
after the DEFINE CLASS statement and before the first FUNCTION or
PROCEDURE statement. A single PRG can have one or more class
definitions in it. They're instantiated using either
followed by
or you can skip the SET CLASSLIB and SET PROCLIB statements and
use a shorter approach:
If you have parameters in a class's Init method, you can add the
parameters at the end of the CREATEOBJECT() or NEWOBJECT()
function call and pass them directly to the Init constructor. Inside the
class, THIS refers to a property or method of the class itself. That's about
the size of creating and using classes in FoxPro.
Namespace PinterConsulting
Get
Return _FileName
End Get
Set(ByVal Value)
_FileName = Value
End Set
End Property
End Class
Public Class Objects
Get
Return _FirstName
End Get
_FirstName = Value
Case "Bob"
Case "Fred"
Case "Eddie"
Case Else
End Select
End Set
End Property
Get
Return _LastName
End Get
_LastName = Value
End Set
End Property
Get
End Get
End Property
End Class
End Class
End Namespace
I've added a form in the same project that uses the classes. Notice that
because the project name was NameSpaceDemo and the NameSpace
was PinterConsulting, the name for the Imports statement (which is like
SET CLASSLIB ... ADDITIVE) is the concatenation of the two.
Listing 1.4 shows the form code:
Imports NameSpaceDemo.PinterConsulting
Inherits System.Windows.Forms.Form
Handles MyBase.Load
End Sub
Handles txtFirst.TextChanged
NameMgr.FirstName = txtFirst.Text
Label1.Text = NameMgr.FullName
End Sub
Handles txtLast.TextChanged
NameMgr.LastName = txtLast.Text
Label1.Text = NameMgr.FullName
End Sub
Handles txtFileName.TextChanged
End Sub
End Class
Note that I could have left out the Objects class because it only
contains one nested class. But I could have added more classes within
Objects, and they would also have been exposed via IntelliSense.
This is why I say that IntelliSense is merely nice to have in FoxPro, but in
Visual Basic .NET you'd be dead without it. But I can imagine complex
projects that need namespaces to document functionality of class
members and classes, and it's just a naming convention. I just can't
escape concluding that FoxPro works just fine without namespaces and
wouldn't be enhanced if we had them. I understand the theory, but isn't
anyone else out there cognizant of the divergence between theory and
reality, and gutsy enough to eschew the theory?
cn.Open()
da.Fill(ds)
DataGrid1.DataSource = ds.Tables(0)
LOCAL cn As String
For years, Visual Basic developers got used to the idea of trapping the
assignment of a value to a property and doing something when that
assignment occurred. For example, I used to use a communication
package that used the following syntax to read data from a telephone
line:
OModem.Setting = 14
The way this worked was that the moment of assigning a value to a
property was trapped in what is called the Property Set method. At
that instant, other code can be called. So they used that when they
couldn't declare a "Do-it" method. It was a workaround for Visual Basic's
inability to give us any way to simply write oModem.Read. After a while,
the workaround seemed normal. Goofy but true.
and press Enter, Visual Basic will add complete the routine by adding the
following six lines of code:
Public Property FullName() As String
Get
End Get
End Set
End Property
You have to add three more lines of code, one before the routine and two
within it, to end up with this:
Get
Return _FullName
End Get
End Set
If you have two properties, you'll have two of these. If you have 20
properties, you'll have 20. Notice that a separate private variable (by
convention the name is the property procedure's name preceded by an
underscore) is required in order to store the value manipulated by the
property procedure, and it must be declared outside of the property
procedure.
This is the equivalent of the Assign and Access methods that FoxPro
allows you to create when you add a property to a class. As you may
know, you can add code to the Assign method code to do other tasks
when the property is assigned a value. That's the idea behind property
procedures.
In Visual Basic .NET, you can't see either class properties or public class
variables in the Properties sheet for the class. However, you can't see
public class variables in the Properties sheet. And you can only see them
in subclasses that inherit from your classes. That's right; to get what you
call a property in FoxPro, you must create a public property procedure in
Visual Basic .NET. Strange but true. So even if you don't need the
Assign and Access methods (called Getters and Setters in Visual
Basic), you need property procedures. The fact that Visual Basic writes
most of the code diminishes the pain, but I think it's goofy, and hope that
it will be replaced by FoxPro's much simpler and clearly adequate
counterpart some day.
Forms
Forms are classes in Visual Basic .NET. If you refer to one in a menu, it
will take either two or three lines of code to display the form:
Frm.Show
Do form Customer
However, have you ever looked at the structure of an SCX file? It has
exactly the same structure as a VCX file. And in fact, if you save a form
as a class, the code to run the form changes to this:
Frm.Show()
So they're more similar than you might have thought.
However, sometimes it's useful to write code that is saved as part of the
form's initialization sequence for example, using property values to open
tables and populate some standard controls. If you have such code, you
can place it in the Public Sub New() method, after the word
InitializeComponent(). It's the only place in the Form Designer generated
code that you can include code that won't be overwritten by the designer.
LPARAMETERS nButton, nShift, nXCoord, nYCoord
'TextBox1
'
Me.TextBox1.Location = New
System.Drawing.Point(110, 54) Me.TextBox1.Name =
"TextBox1"
Me.TextBox1.TabIndex = 0
Me.TextBox1.Text = "TextBox1"
Me.Controls.Add(Me.TextBox1)
ByVal e As System.EventArgs) _
<span class="docEmphStrong">Handles
TextBox1.LostFocus</span> TextBox1.ForeColor =
Color.Black TextBox1.BackColor = Color.White End Sub
ByVal e As System.Windows.Forms.MouseEventArgs)
_
Handles MyBase.MouseDown
End Sub
PROCEDURE CoordinateTables
Option Strict On
Imports System.Data.SqlClient
Namespace DataAccessLayer
Return _MainTable
End Get
End Set
End Property
Return _KeyField
End Get
End Set
End Property
Try
End Try
End While
End Function
End If
Try
End If
End Sub
End Class
End Namespace
Option Strict On
Imports System.Data.SqlClient
Imports DataAccessLayer
ByVal e As System.EventArgs) _
End Sub
ByVal e As System.EventArgs) _
End Sub
ByVal e As System.EventArgs) _
ByVal e As System.EventArgs) _
Handles btnUpdate.Click
m_DAL.UpdateDataSet(ds.GetChanges())
frmMain_Load(Me, New System.EventArgs) End Sub
End Sub
As System.Windows.Forms.KeyEventArgs) _
ByVal e As System.EventArgs) _
BindGrid()
End Sub
ByVal e As System.EventArgs) _
Handles grd.CurrentCellChanged
grd.Select(grd.CurrentCell.RowNumber) If TypeOf
(grd.Item(grd.CurrentRowIndex, 2)) Is DBNull Then
grd.Item(grd.CurrentRowIndex, 2) = _
grd.Item(grd.CurrentRowIndex, 0) End If
End Sub
Private Sub grdClick( _
ByVal e As System.EventArgs) _
Handles grd.Click
BindGrid()
End Sub
Handles m_DAL.ConnectionCompleted
frmStatusMessage.Close() End Sub
Handles m_DAL.ConnectionStatusChange
frmStatusMessage.Show(status) End Sub
Sub BindGrid()
With grd
End With
End Sub
Sub GetDataSet()
End If
End Class
System.Threading.Thread.CurrentThread.Sleep(500)
Application.DoEvents() End Sub
In Visual Basic .NET, the #Const compiler directive also exists, and the
#IF/#ELSE/#ENDIF construct works much like its counterpart in FoxPro,
although not to support multiple language versions because there's only
one at this time; besides, Visual Basic doesn't allow code that isn't
syntactically correct to exist in the source code, as it compiles
automatically when you close a code window.
FoxPro stores its data in either DBF files or in cursors. DBFs have a
header of about 512 bytes that describes the nature of the table and its
membership, if any, in a data base container (DBC). It then uses 32 bytes
per field to describe all of the fields in the table. What follows are fixed-
length records with a "delete byte" at the front, one per record. Cursors
are in-memory representations of tables, having the same format except
for the database container (DBC) information, which doesn't apply. When
you USE a table or create a cursor using CREATE CURSOR or as the
result of a SQL SELECT statement, FoxPro reads the header and uses its
contents to interpret the fixed-length records that follow. BROWSE displays
the table rows in a grid-like format.
Visual Basic .NET has no native data storage format. It always treats
data as a foreign object. It usually reads it into a dataset, which is an XML
representation of the data.
In its simplest form, XML consists of an XML header (a single line that
says "I'm an xml string"), followed optionally by a Schema that describes
the rows that follow, followed by a hierarchy of rows and fields that
describe the table. A simple example follows:
<?xml version = "1.0" encoding="Windows-1252" standalo
<VFPData>
xmlns:msdata="urn:schemas-microsoft-com:xml-msdat
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name">
<xsd:simpleType>
<xsd:restriction base="x
<xsd:maxLength value=
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="phone">
<xsd:simpleType>
<xsd:restriction base="x
<xsd:maxLength value=
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:anyAttribute namespace="http://www.w3
processContents="lax"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<customer>
<name>Les Pinter</name>
<phone>650-344-3969</phone>
</customer>
</VFProData>
It looks scary, but if you saw the FoxPro DBF header it would be equally
scary. Oh, what the heck. Figure 1.6 shows what the data that was
displayed in XML looks like as a FoxPro .DBF.
As you can see, neither one is really a "table." The program reads the file
and uses it for whatever display mechanism you choose. As you can see,
the tags that bracket each data element occupy quite a bit of space,
compared to the positional format that fixed-length records permit; on the
other hand, trimmed strings mean that extra blanks aren't transmitted. On
balance, DBFs are somewhat smaller than their XML counterpart. So the
idea that XML is less of a table than a DBF is just silly.
But the idea that XML is less efficient as a data storage mechanism is
absolutely accurate. In fact, DBFs are extraordinarily efficient, especially
when combined with the structural .cdx index files supported by FoxPro.
I've demonstrated a SELECT that returned 30 matching records from
more than a million in less than a tenth of a second. You can't get
performance like that from SQL Server, which is a much more expensive
data store. In some sense, XML is a dumbed-down DBF. Still, it does the
job.
Visual Basic .NET doesn't have tables or cursors. It has datasets. You fill
a dataset by creating a SQLCommand or an OLEDBCommand object,
specifying its SelectCommand CommandText, executing the SELECT,
and then calling the Fill method of the Command object or
DataAdapter object to fill the dataset. If that looks more complicated to
you than USE (Table), you're right.
But wait, there's more. A dataset can contain several tables; in that sense
it's more like a form's Data Environment. In fact, it's a lot like a form's
Data Environment. A dataset can also contain indexes and relations, just
as the DE does.
Things stop being equally simple about here. You can bind a recordset to
a DataGrid, but you have to tell it which of the tables in the recordset to
bind to first. If you don't, you'll get a little plus sign that you click on to
display all available tables and choose one. Or you can specify the table.
Table(0) is the first table, so if there's only one table, it's always the
same code. You can also select the DefaultView of the dataset, which
is table(0), and assign that to the DataGrid's DataSource:
SqlDataAdapter1.Fill(DsEmployee1)
Dim dv As DataView
dv = DsEmployee1.Tables(0).DefaultView
DataGrid1.DataSource = dv
But that's not entirely true. If you use DBFs it's not necessary. But if you
use SQL or a Web service as the data store, you have to build a
mechanism for executing the UPDATE statement that applies the changes
that you made in the local cursor to the data store. So it's harder in Visual
FoxPro as well.
The fact is that sending back new records or updates to a local table in
Visual Basic .NET is exactly as difficult as sending them to SQL Server.
So the programming cost saving of working with local tables in Visual
Basic .NET is gone. You might as well print out the bill for some SQL
Server licenses and hand them to your client.
Data Binding
In FoxPro, when you change a field's value on a screen and the
corresponding data field in the underlying table is changed, you're taking
a lot for granted. What's happening is called data binding.
Visual Basic .NET doesn't have data binding to the data store. Not really.
What Visual Basic .NET does have is binding to the dataset, the XML
string that holds the data you got from your data source. Assume you've
built a form with a text box on it and then returned a typed dataset. (See
Chapter 4, "A Visual Basic .NET Framework," for more information on the
Customers table containing Phone fields.) Select the text box on the
form, press F4 to open the Properties window, and click on the plus sign
beside the Databindings property to open the two fields under the
heading. Select Text, and then select Customers.Phone from the list of
available dataset classes that automatically appears.
This will cause the dataset's Phone value to be copied to the text box's
Text property when you get values for the rows in the dataset, and
conversely it will move any value that's typed into the text box into the
dataset's Phone column. That's what Visual Basic .NET calls data
binding.
TRY...CATCH
The syntax for TRY...CATCH is slightly different for Visual Basic .NET
than for Visual FoxPro.
TRY
Code here
Catch To oEx
Finally
Try
Code here
Finally
END Try
Similarly, you can print out oEx.Message + CHR(13)+oEx.Source to
see the error message and offending code.
In the code that appears in this book, we have left out error trapping in
most code samples. That doesn't mean that we recommend leaving it out
of your code. In fact, Try...Catch blocks are an excellent idea, and should
be used in every case where a user or system error is possible. (It can
even be helpful in finding your own coding errors during debugging,
although that's not the purpose of Try...Catch.) But we've decided to
focus on the main purpose of each code fragment, and adding error
trapping to a single line of code tends to make the code harder to read.
So with regard to error trapping, do as I say, not as I do.
Debugging
Modules
Threads
Call Stack
Me
Command window
Locals
Autos
Running Documents
Breakpoints
Each of these windows gives you some of the information about your
program's state at the breakpoint. You can either go to the command
window in Immediate mode and print the contents of individual Variables
and object properties, type the name of the object in a watch window, or
look in the Locals window to see if it's there already.
It's clear that both languages offer excellent error trapping and debugging
capabilities.
Summary
By now, I hope you've come to the same conclusion I have, which is that
Visual Basic .NET is not nearly as different as it looks. The few things
that appear truly strange are not really very important; the use of events,
when it's necessary, can be reduced to a six-step cookie-cutter
procedure; property procedures practically write themselves, and do the
same thing as FoxPro properties do; and the additional effort get to your
data can be relegated to a data access layer class that does the same
thing FoxPro does, so that you don't ever have to think about it.
CustomerID Integer, ;
Name Char(30), ;
Address Char(30), ;
City Char(30), ;
Zip Char(10), ;
Phone Char(10), ;
Balance Numeric(9,2),;
LastOrder Date(8) )
There are a few FoxPro defaults that exist at the environment level that
have to be changed. First, before you can insert a date into a table, you
have to make sure that you've set the STRICTDATE setting to zero. I
don't know why this setting defaults to the SQL Server syntax in a default
installation, but it does. Also TALK defaults to ON, and displays all
variable assignments in the _Screen background. Finally, MULTILOCKS
must be set to ON in order to enable buffering. So issue these three
commands either in the command window, or in the form's LOAD event
before anything else happens:
SET STRICTDATE TO 0
SET MULTILOCKS ON
After you've changed these settings, the following command will put one
record into the new table:
Based on this table structure and this record, a typical customer screen
might look like the one shown in Figure 2.1.
Figure 2.1. A simple application screen.
To build our new application, we first need to create a class library with
subclasses of the FoxPro base control classes. Type the following in the
command window, using Ctrl+W to close the resulting designer window
after each command:
TIP
Field Mapping
Select Tools, Options, Field Mapping and match the controls shown in
Table 2.1 with the data types used in your Customers table. If you cut and
paste the class name Pinter.vcx into the Class Location field and use
Alt+A to save each field mapping after you fill in the three fields in the
Field Mapping dialog, the entire process of creating the classes and
setting the mappings should take two minutes or less.
Form Methods
I know that I'll want to enable and disable controls and command buttons,
so I might as well write methods to do this before I go any further.
In Visual FoxPro, you can traverse all controls in the form's Controls
collection and set values based on the names of the form classes. I
added a form property called InputClasses and populated it with the
names of the input control classes in pinter.vcx:
InputClasses: MyText,MyEdit,MyCheck,MyCombo,MySpin
PARAMETERS OnOff
WITH THISFORM
IF UPPER(Ctrl.Class) $.InputClasses
Ctrl.Enabled = OnOff
ENDIF
ENDFOR
.Buttons(OnOff)
ENDWITH
PARAMETERS OnOff
WITH THISFORM
.cmdSave.Enabled = OnOff
.cmdCancel.Enabled = OnOff
ENDWITH
Because the only controls that are enabled or disabled are the ones in
the list, this approach permits us to put other controls (for example, list
boxes) on the form without enabling or disabling them when inputs are
enabled or disabled.
Open the form, select View, Toolbars, and click on the Form Controls
toolbar. Click on the three little books and select pinter.vcx. This
changes the available controls to include only those contained in our
class library. Drag and drop nine instances of the MyCommand control to
the bottom of the form. Change their names, captions, and their
Enabled property as shown in Table 2.2.
I've resized the form and changed both the form's Name and its Caption
to Customers, its AutoCenter property to .T., and its ControlBox
property to .F.. It's beginning to look pretty good. Now it's time to code
the buttons.
Given that this is a single-user application, you'd think that all I have to do
is change the table's Buffering property to 3 (optimistic record),
append a blank, and refresh the screen. However, APPEND BLANK
moves the record pointer to the phantom record. If the user cancels, I
want to redisplay the same record that was visible before the user clicked
on Add. So I need to add a BeforeAdd property to the form by selecting
Form, Add Property Menu. When that's done, the code for the Add button
is this:
WITH THISFORM
SELECT ( [CUSTOMERS] )
.BeforeAdd = RECNO()
CursorSetProp ( [Buffering], 3 )
APPEND BLANK
.Refresh
.Inputs(.T.)
ENDWITH
FUNCTION NextKey
PARAMETERS pTableName
pTableName = UPPER(pTableName)
SaveAlias = ALIAS()
USE KEYS IN 0
ENDIF
SELECT KEYS
IF NOT FOUND
APPEND BLANK
ENDIF
SELECT ( SaveAlias )
RETURN LastKey
If you can be guaranteed that every table will have a unique integer key,
you can include a call to this function in your code, using this:
The code for the Edit button is almost identical to the code for the Add
button, except that the APPEND BLANK is not needed. Strictly speaking,
saving the RECNO() value in BeforeAdd isn't necessary either, but it
makes the Cancel button code easier to write because we don't need to
know whether we're canceling an Add or an Edit:
WITH THISFORM
SELECT ( [CUSTOMERS] )
.BeforeAdd = RECNO()
CursorSetProp ( [Buffering], 3 )
.Refresh
.Inputs(.T.)
ENDWITH
WITH THISFORM
SELECT ( [CUSTOMERS] )
TableUpdate(.T.)
CursorSetProp ( [Buffering], 1 )
.Inputs(.F.)
ENDWITH
WITH THISFORM
SELECT ( [CUSTOMERS] )
TableRevert(.T.)
CursorSetProp ( [Buffering], 1 )
.Inputs(.F.)
GO .BeforeAdd )
ENDIF
.Refresh
ENDWITH
SELECT ( [CUSTOMERS] )
DELETE
IF NOT EOF()
SKIP
ELSE
GO BOTTOM
ENDIF
.Refresh
ENDWITH
cmdPrevious::Click
SELECT ( [CUSTOMERS] )
IF NOT BOF()
SKIP -1
IF BOF()
GO TOP
ENDIF
ENDIF
THISFORM.Refresh
cmdNext::Click
SELECT ( [CUSTOMERS] )
IF NOT EOF()
SKIP
IF EOF()
GO BOTTOM
ENDIF
ENDIF
THISFORM.Refresh
SELECT ( [CUSTOMERS] )
WhereWasI = RECNO()
ENDIF
THISFORM.Refresh
Finally, to close the form, the form's Release method is all we need:
THISFORM.Release
Now, change the font for the MyText class in pinter.vcx, and notice
that it changes every text box on the screen. That's a simple
demonstration of the power of objects. But wait, there's more.…
Since we used the power of objects for our control classes, why not do
the same thing with the form? Use MODIFY FORM Customers to open
the form. Highlight all of the labels and controls except for the command
buttons and cut them (don't copy, but cut them to keep them in the
clipboard for later), and then choose Save As Class. Pick Pinter.vcx
as the class library, use the name FlatFileForm for the class, and
save it (see Figure 2.2). This also saves the form's properties and
methods.
Figure 2.2. Saving the form and buttons as a class.
Now we're ready for some magic. In the command window, type
ERASE CUSTOMERS.SC?
to erase the SCX and SCT files containing the Customers form. Then,
select Tools, Options, Forms from the IDE menu, click on Form Template,
and select FlatFileForm from the Pinter.VCX Class Library. Click
OK to close the dialog.
Now, type
The original code that we wrote for the form class is still there; however,
it's specific to a single table. To make it generic, add a TableName and
an IndexTag property to the FlatFileForm class. Then, change all
instances of [Customers] in the class code to
(THISFORM.TableName). You can also change the FlatFileForm
class Caption property to "Please supply a title".
TIP
WITH THISFORM
SELECT 0
USE ( .TableName )
ENDIF
ENDWITH
USE IN ( THISFORM.TableName )
Finally, open the Customers form with MODIFY FORM CUSTOMERS and
add Customers as the TableName property and the form Caption. If
you've added an index for CustomerID, you can enter the name of the
tag in the IndexTag property.
Cosmetics
Before you run the form, there are a few things you can do to tidy up your
creation. For one thing, the tab order is probably not right. Choose Tools,
Options, Forms to open the Forms dialog, and change the Tab Ordering
to By List. Now, open the form and click on View, Tab Order. Click on the
By Row button. Now, find the cmdAdd button and drag it to the top of the
list. Drag the cmdEdit button to the second position in the list, and click
OK.
Next, open the Properties sheet, select the form, and change the
BorderStyle property to something other than 3. We don't really do
resizing of this kind of form, so if users try to do so it will look funny. We'll
just nip it in the bud.
Open the form in the Form Designer and look at the code. There isn't
any! That's the right way to build forms in Visual FoxPro.
This simple example used DBFs, the simplest way to deal with data in
FoxPro. The CursorAdapter in Visual FoxPro 8 makes it about as easy
to build a SQL Server or Web Service application as it is in Visual Basic
.NET. I purposely didn't use the CursorAdapter in this example. We'll
deal with the CursorAdapter in Chapter 6, "Data Access" and Chapter
7, "XML."
TIP
Next, we'll do the same thing we did in this example again in Visual Basic
.NET. It's a little different, but the concepts are the same.
Building a Simple Application in Visual Basic
.NET
The simplest Visual Basic .NET application is a single form managing a
single table. It doesn't matter whether it's a "local" table stored in a
FoxPro DBF or an Access MDB file or a table in an MSDE or a SQL
Server database. The coding is exactly the same.
There is a command window in Visual Basic .NET, but it has very limited
use. To create a new table, we'll first need to add a new database. Use
Ctrl+S to open the Server Explorer, and expand the tree until SQL Server
is visible. Expand its tree, right-click on Databases, and select New
Database. Supply the name Chapter2, and use sa and blank for the
userid and password respectively (see Figure 2.3).
The structure is the same. The only peculiarity is that the database
offered to store NULL values for any column entries not specified in an
INSERT statement. We absolutely don't want that. I've wasted a
significant portion of my professional life dealing with null fields, provided
on the off chance that I might want to know whether those fields were
specified or not. Blank is blank, and that's always been good enough in
my line of work. So don't forget to uncheck the nulls.
At the bottom of the Table Designer, there is a toolbar with five choices:
Relationships
These are largely features that are supported either by the FoxPro Data
Environment, or by the database container. Select the CustomerID field
and click on Set Primary Key to create an index. Without this, Visual
Basic .NET has no clue how to locate a specific record for updating or
deleting because there is no RECNO() in SQL Server.
The other features won't enter into our simple example in this chapter,
but you can be sure they come in handy at some point in a real-world
application.
To enter a record into the table, right-click on the table name in the
Solution Explorer and select Retrieve Data from Table. The resulting grid
allows us to enter one row at a time. Type in the same values entered for
CUSTOMERS.DBF, as shown in Figure 2.5.
Now for a little surprise: Open the Server Explorer with Ctrl+Alt+S, select
the Customers table in the Chapter2 database, and drag it onto Form1.
You geta DataAdapter. What's a DataAdapter?
In our Visual FoxPro example, the simple act of issuing the command
USE CUSTOMERS, or USE (THISFORM.TableName) in the form class,
in effect opened the data source and returned the entire table. There's a
command in Visual Basic .NET that does the same thing. It's actually not
a command; it's the FILL method of the DataAdapter. It takes two
parameters; the first is the name of a dataset, and the second is a name
to give the first table in the dataset.
Creating a Dataset
To create a dataset, select Generate Dataset from the Data pad of the
IDE menu, using dsCustomers as the dataset name. You'll notice that a
file named dsCustomers.xsd has been added to the project. There's
more to it than that, but I'll tell you later. An instance named
dsCustomers1 has been added to the form, much as a TextBox class
added to your form in Visual FoxPro will be named TextBox1.
Double-click on the form to open its Load event and type the following
line:
SQLDataAdapter1.Fill(dsCustomers1,"Customers")
Form Methods
We'll need the same two methods used in the FoxPro application.
They're almost identical, except that in Visual Basic .NET you can't refer
to a variable until it's been declared with Dim or some other modifier:
Next
Buttons(Not OnOff)
End Sub
Public Sub Buttons(ByVal OnOff As Boolean)
Next
End Sub
Because the command buttons begin with "cmd", I can direct the code to
manipulate only command buttons or only controls other than command
buttons.
I've resized the form and changed both the form's Name and its Text
property to Customers, its StartPosition to CenterScreen, and its
ControlBox property to False. The properties and settings are either
almost identical to those of FoxPro or easily recognizable.
Handles cmdAdd.Click
Adding = True
BindingContext(DsCustomers1, "Customers").AddNew()
Inputs(True)
End Sub
A Visual Basic .NET routine to add a unique key would be nice. However,
if you define the CustomerID as an IDENTITY field in SQL Server, the
next available key value is automatically returned. FoxPro 8 also has this
capability. You can also write your own, as we did earlier, and this can be
useful for systems that can go offline and then synchronize the tables
upon reconnection.
The Edit button code is extremely simple because datasets keep track of
their changes automatically; the only kind of Buffering mode in Visual
Basic .NET datasets is mode 5:
Handles cmdEdit.Click
Inputs(True)
End Sub
To save any changes made by the user, we call the Update() method of
the corresponding DataAdapter, and then disable input fields:
Handles cmdSave.Click
Inputs(False)
Try
.EndCurrentEdit()
End With
SqlDataAdapter1.Update(DsCustomers1, "Customer
DsCustomers1.AcceptChanges()
BindingContext(DsCustomers1, "Customers").Posi
End Try
End Sub
To cancel an Add or any changes made by the user, we just disable the
input fields:
Handles cmdCancel.Click
Inputs(False)
Try
.CancelCurrentEdit()
End With
Catch oEx As Exception
End Try
End Sub
Handles btnDelete.Click
Try
BindingContext(dsCustomers1, "Customers").Remo
dsCustomers1.Tables("Customers").AcceptChanges
End Sub
The Previous and Next buttons are one-line commands in Visual Basic
.NET, because there's no need to check for BOF() or EOF() before
moving the BindingContext's position, the Visual Basic read-write
equivalent of RECNO():
Handles cmdPrevious.Click
BindingContext(DsCustomers1, "Customers").Position
End Sub
Handles cmdNext.Click
BindingContext(DsCustomers1, "Customers").Position
End Sub
Handles cmdBrowse.Click
Width = 445
cmdBrowse.Text = "Browse"
Else
Width = 660
cmdBrowse.Text = "Hide"
End If
End Sub
Handles DataGrid1.CurrentCellChanged
BindingContext(DsCustomers1, "Customers").Position
DataGrid1.CurrentRowIndex
End Sub
Finally, to close the form, the form's Close() method is the Visual Basic
.NET equivalent of THISFORM.Release:
Close()
In the next chapter, we'll use a data access layer to add SQL support to a
generic Visual FoxPro template, so that no code changes are needed to
switch between DBF and SQL access. Then, in Chapter 4, "A Visual
Basic .NET Framework for SQL Server," we'll build a generic inheritable
form for Visual Basic .NET that treats all data sources equally.
Chapter 3. Building a Visual FoxPro
Application for SQL Server
IN THIS CHAPTER
Why Three-Tier?
What's Next?
Even if you haven't started using three-tier data access in FoxPro, you
certainly have heard of it. What's the big deal? Is this something that you
need to learn? In this chapter, you'll discover that
It's easy.
The library described in this chapter can be used with your very next
project.
In this chapter, we'll build a data access layer to communicate with either
DBFs or SQL Server. And we'll build it in such a way that there is
absolutely no code to change when you move from DBFs to SQL tables.
We'll even include an upsizing wizard to migrate the data for you. We'll
talk about the things that you don't want to do in SQL if you want to
simplify programming (always a good thing). We'll use a data access
layer, which gives you the ability to use DBFs, SQL Server, a
WebConnection XML server, or XML Web services built in Visual FoxPro
8, the best upgrade yet. The code for this chapter is written to be
compatible with Visual FoxPro 7, but in subsequent chapters we'll add
features only available in versions 8 and higher. It might surprise
Microsoft, but not everyone has the latest version of their languages.
Why Three-Tier?
Three-tier is a variant of n-tier: A calls B calls C, and so on. Each one
does a part of the task. With server farms and ASP applications, there
can be several data tiers, a page generation tier, and so forth. But for our
purposes, three-tier is generally sufficient. In the usual three-tier
diagrams (which we'll dispense with here), A is your form, B is a data
access layer, and C is the place where the data is storedusually DBFs in
our world, but that's changing, and that's where the data access layer
comes in.
In traditional FoxPro applications, our forms contain the code that gets
and stores data. Our code snippets are full of SEEK and REPLACE
commands. The problem arises when our client decides that they're tired
of kicking everyone out of the application and rebuilding the indexes, or
redoing a 400-file backup that just failed because a user didn't close the
CONTROL file, or watching an APPEND BLANK take 30 seconds
because the table has 900,000 records and the index is 9MB. DBFs are
great, but they do have drawbacks. And the solution is spelled S-Q-L.
SQL has numerous benefits. When you back up a SQL database, you're
backing up a single file. Backup can be run while users are in the system.
And RESTORE is also a one-line command.
Security is another issue with FoxPro tables. Anyone with access to the
DBF directory on the server can see your FoxPro tables. SQL Server, on
the other hand, has complex security built in. So you can decide who
does what. In today's increasingly risky environment, users can and will
demand improved security. SQL Server is a good way to accomplish it.
So your client is sold. Install SQL Server. You can run SQL Server on
your development machine just fine; in fact, it's a great idea. Be sure to
install the Developer Edition, which has a Management Console. If all
you have is MSDE, it will work fine, but you have to create the database,
indexes, and logins programmatically, and it's just a little harder to learn
some SQL tasks nonvisually.
SQL Server runs as a service. It "listens" for requests from workstations,
does what is asked of it, and sends any result set back to the
workstation. There is no index traffic because the indexes don't come
back with the results. Most of the slowdown you might have experienced
in FoxPro apps on a LAN are due to network traffic, so solving the
slowdown problem can be sufficient motivation for migrating to SQL
Server.
So you've installed SQL Server, and you need to migrate your application
to use SQL Server tables. First, you have to migrate the data. There are
several ways to do this, and all of them have problems. You can use the
SQL Upsizing Wizard, but the resulting SQL tables can cause serious
programming headaches. There's a DTS utility that is installed when you
load SQL Server, and if you like writing your data recoding routines in
Basic, go right ahead. I prefer FoxPro. So your best bet is to write your
own data migration program. I've included one in the code for this
chapter.
What's wrong with the Upsizing Wizard? For one thing, it defaults to
permitting NULLs as values in uninitialized fields. If you've never run into
NULLs, consider yourself lucky. Statisticians need to know whether a
value of zero is a reported value or simply someone who didn't answer
the questionfor example, What is your age? You can't calculate average
age by summing ages and dividing by zero if half of the respondents
didn't want to answer. So you have to know which are missing values.
SQL Server allows for missing values, and in fact defaults to them. But
they nearly double the programming burden. ASP code goes bonkers
with nulls. So unless you really, truly care about missing values, you
absolutely don't want to use NULLs. That's why the preferred way to
declare a column in T-SQL is
To get a head start, open each of your application's tables and create a
primary key field if it doesn't already have one (child tables are good
candidates). Use the MODIFY STRUCTURE command, add the PKFIELD
column name (you can use PKFIELD as the PRIMARY KEY column for
every table if you want to), and then use this to add unique keys:
or
COPY TO CUSTOMER
COPY TO EMPLOYEE
When you have your two tables, you should add integer keys to the
tables using MODIFY STRUCTURE. In this case, use PKFIELD
(Integer) for both tables, and make it the first field in the table. Before
leaving the schema designer, add PKFIELD as an index tag, or just type
the following in the command window:
Finally, you can set the database name in the SQL ConnectionString
and run the LoadSqlTables program to load your tables.
If you want to use your own tables from your own database, you can
copy them to DBFs very easily, using the procedure shown in Listing 3.1;
substitute your names for your database, userID, and password.
PROCEDURE SQLToDBF
PARAMETERS TableName
COPY TO ( TableName )
ENDPROC
Sample usage:
SQLToDBF ( [Customers] )
Handle = SQLStringConnect ( ;
"driver={SQL Server};server=
(local);database=Master;pwd=sa;uid=;") Result =
SQLExec ( Handle, "CREATE DATABASE
MYDATABASE" ) SQLDisconnect(0)
Handle = SQLStringConnect ( ;
"driver={SQL Server};server=
(local);database=MyDatabase;pwd=sa;uid=;")
IF Handle < 1
ReservedWords = ;
[,DESC,DATE,RESERVED,PRINT,ID,VIEW,BY,DEFAU
ASORT(laDBFS,1)
FOR I = 1 TO ALEN(laDBFS,1)
_VFP.Caption = [Done]
PROCEDURE <span
class="docEmphStrong">LoadOneTable</span>
LOCAL I
cRecCount = TRANSFORM(RECCOUNT())
IF ALIAS() $
[COREMETA/DBCXREG/SDTMETA/SDTUSER/FOXU
? [Skipping ] + ALIAS()
RETURN
ENDIF
IF TYPE(Fld) = [G]
LOOP
ENDIF
dta = &Fld
IF cDta = [/ /]
cDta = []
ENDIF
Cmd = Cmd + ['] + cDta + ['] + [, ]
IF cDta = [/ /]
cDta = []
ENDIF
Cmd = Cmd + ['] + cDta + ['] + [, ]
Cmd = Cmd
ENDCASE
ENDFOR
Cmd = LEFT(Cmd,LEN(cmd)-2) + [ )]
? [Error: ] + Cmd
SUSPEND
ENDIF
ENDSCAN
WAIT CLEAR
PROCEDURE <span
class="docEmphStrong">CreateTable</span>
LOCAL J
Cmd = [CREATE TABLE ] + ALIAS() + [ ( ]
AFIELDS(laFlds)
FOR J = 1 TO ALEN(laFlds,1)
IF laFlds(J,2) = [G]
LOOP
ENDIF
N = TRANSFORM(laFlds(J,3)) D =
TRANSFORM(laFlds(J,4)) Cmd = Cmd + [Numeric(] +
N + [,] + D + [) NOT NULL DEFAULT 0, ]
Cmd = LEFT(Cmd,LEN(cmd)-2) + [ )]
IF lr < 0
_ClipText = Cmd
? [Created ] + ALIAS()
ENDPROC
For each DBF that's not in the "Skip These Tables" list at
the top of the LoadOneTable routine, the program issues
a DROP TABLE "Name" command, followed by a CREATE
TABLE command, which it builds. It then scans all records
in the DBF and creates and executes an INSERT statement
for each record. Users are often amazed at how fast this
loads their data. I'm not. It's FoxPro.
MODIFY PROJECT Chapter3
WITH _Screen
WITH _Screen
.RemoveObject ( [Title1] )
.RemoveObject ( [Title2] )
ENDWITH
DEFINE CLASS Title AS Label
Visible = .T.
BackStyle = 0
FontSize = 48
Height = 100
Width = 800
Left = 25
PROCEDURE Init
PROCEDURE <span
class="docEmphStrong">ErrTrap</span>
LPARAMETERS nLine, cProg, cMessage, cMessage1
OnError = ON("Error")
ON ERROR
IF NOT FILE ( [ERRORS.DBF] )
USE ERRORS IN 0
ENDIF
SELECT Errors
_Screen.RemoveObject ( [Title2] )
_Screen.RemoveObject ( [Title1] )
ENDIF
CLOSE ALL
RELEASE ALL
CANCEL
ELSE
ON ERROR &OnError
ENDIF
SET SYSMENU TO
SET SYSMENU AUTOMATIC
oDataTier.AccessMethod = ;
* Classes to enable/disable
.cmdFind.Enabled = OnOff
.cmdClose.Enabled = OnOff
.cmdClose.Cancel = OnOff
PROCEDURE Load
USE IN ( .MainTable )
ENDIF
ENDWITH
ENDPROC
PROCEDURE cmdAdd.Click && Adds a new record,
autopopulating the key field
WITH THISFORM
cNextKey = oDataTier.GetNextKeyValue ( .MainTable )
SELECT ( .MainTable )
.BeforeAdd = RECNO()
CURSORSETPROP( [Buffering], 3 )
APPEND BLANK
IF TYPE ( .KeyField ) <> [C]
.Inputs ( .T. )
.Adding = .T.
ENDWITH
ENDPROC
.BeforeAdd = RECNO()
CURSORSETPROP( [Buffering], 3 )
.Inputs ( .T. )
.Adding = .F.
ENDWITH
ENDPROC
TABLEUPDATE(.T.)
CURSORSETPROP( [Buffering], 1 )
.Inputs ( .F. )
TABLEREVERT(.T.)
CURSORSETPROP( [Buffering], 1 )
.Inputs ( .F. )
PROCEDURE cmdFind.Click
.Buttons ( .T. )
ENDIF
ENDIF
ENDWITH
ENDPROC
THISFORM.txtKeyField.Caption = ; TRANSFORM (
EVALUATE ( THISFORM.MainTable + [.] ; +
THISFORM.KeyField ))
IF NumWords > 4
.ColumnCount = NumWords
.RecordSource = THISFORM.ViewName
.RecordSourceType = 1
GridWidth = 0
FOR I = 1 TO NumWords
.Columns(I).Header1.Caption = THISFORM.Heading
(I) GridWidth = GridWidth + VAL(
THISFORM.ColWidth(I) ) FldName =
THISFORM.ViewName + [.] + THISFORM.Field (I)
.Columns(I).ControlSource = FldName
ENDFOR
Multiplier = ( THIS.Width / GridWidth ) * .90 &&
"Fudge" factor FOR I = 1 TO NumWords
Ctrl.Enabled = .T.
PROCEDURE <span
class="docEmphStrong">Load</span>
WITH THISFORM
IF EMPTY ( .TableName )
.ColWidths = [1,1,1,1,1]
ENDIF
IF EMPTY ( .ColHeadings )
.ColHeadings = .ColNames
ENDIF
.Access = oDataTier.AccessMethod
oDataTier.CreateView ( .TableName )
ENDWITH
ENDPROC
PROCEDURE <span
class="docEmphStrong">Unload</span>
WITH THISFORM
IF USED ( .ViewName )
USE IN ( .ViewName )
ENDIF
RETURN .ReturnValue
ENDWITH
ENDPROC
PROCEDURE <span
class="docEmphStrong">cmdShowMatches.Click</span>
WITH THISFORM
Fuzzy = IIF ( THISFORM.Fuzzy.Value = .T., [%], [] )
STORE [] TO Expr1,Expr2,Expr3,Expr4
FOR I = 1 TO .SearchFieldCount
SELECT ( .ViewName )
ZAP
APPEND FROM DBF([SQLResult])
GO TOP
.Grid1.Refresh
IF RECCOUNT() > 0
.cmdSelect.Enabled = .T.
.Grid1.Visible = .T.
.Grid1.Column1.Alignment = 0
PROCEDURE <span
class="docEmphStrong">cmdClear.Click</span>
WITH THISFORM
FOR I = 1 TO .SearchFieldCount
PROCEDURE <span
class="docEmphStrong">cmdSelect.Click</span>
WITH THISFORM
lcStrValue = TRANSFORM(EVALUATE(.KeyField))
.ReturnValue = lcStrValue
.Release
ENDWITH
ENDPROC
PROCEDURE <span
class="docEmphStrong">cmdCancel.Click</span>
WITH THISFORM
.ReturnValue = []
.Release
ENDWITH
ENDPROC
ENDDEFINE
CREATE FORM FindCust AS EasySearch FROM Pinter
AccessMethod = []
ConnectionString = ;
[Driver={SQL Server};Server=
(local);Database=Mydatabase;UID=sa;PWD=;]
Handle = 0
PROCEDURE <span
class="docEmphStrong">AccessMethod_Assign</span>
PARAMETERS AM
DO CASE
CASE AM = [DBF]
CASE AM = [XML]
IF THIS.AccessMethod = [DBF]
SELECT 0
PROCEDURE <span
class="docEmphStrong">GetHandle</span> IF
THIS.AccessMethod = [SQL]
IF THIS.Handle > 0
RETURN
ENDIF
THIS.Handle = SQLSTRINGCONNECT(
THIS.ConnectionString ) IF THIS.Handle < 1
PROCEDURE <span
class="docEmphStrong">GetMatchingRecords</span>
LPARAMETERS pTable, pFields, pExpr
&cExpr
THIS.GetHandle()
IF THIS.Handle < 1
RETURN
ENDIF
THIS.FillCursor()
ELSE
Msg = [Unable to return records] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
ENDCASE
ENDPROC
PROCEDURE <span
class="docEmphStrong">CreateView</span>
LPARAMETERS pTable
SELECT ( pTable )
AFIELDS( laFlds )
SELECT 0
SELECT ( pTable )
&cExpr
THIS.FillCursor( pTable )
ELSE
Msg = [Unable to return record] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
CASE THIS.AccessMethod = [XML]
PROCEDURE <span
class="docEmphStrong">FillCursor</span>
LPARAMETERS pTable
IF THIS.AccessMethod = [DBF]
RETURN
ENDIF
SELECT ( pTable )
ZAP
APPEND FROM DBF ( [SQLResult] )
USE IN SQLResult
GO TOP
ENDPROC
PROCEDURE <span
class="docEmphStrong">DeleteRecord</span>
LPARAMETERS pTable, pKeyField
IF THIS.AccessMethod = [DBF]
RETURN
ENDIF
PROCEDURE <span
class="docEmphStrong">SaveRecord</span>
PARAMETERS pTable, pKeyField, pAdding
IF THIS.AccessMethod = [DBF]
RETURN
ENDIF
IF pAdding
PROCEDURE <span
class="docEmphStrong">InsertRecord</span>
LPARAMETERS pTable, pKeyField
PROCEDURE <span
class="docEmphStrong">UpdateRecord</span>
LPARAMETERS pTable, pKeyField
FUNCTION <span
class="docEmphStrong">BuildInsertCommand</span>
PARAMETERS pTable, pKeyField
FOR I = 1 TO FCOUNT()
Fld = UPPER(FIELD(I))
FOR I = 1 TO FCOUNT()
Fld = FIELD(I)
Dta = IIF ( Dta = [/ /], [], Dta ) Dta = IIF ( Dta = [.F.],
[0], Dta ) Dta = IIF ( Dta = [.T.], [1], Dta ) Dlm = IIF (
TYPE ( Fld ) $ [CM],['],; IIF ( TYPE ( Fld ) $ [DT],['],;
IIF ( TYPE ( Fld ) $ [IN],[], []))) Cmd = Cmd + Dlm +
Dta + Dlm + [, ]
ENDFOR
Cmd = LEFT ( Cmd, LEN(Cmd) -2) + [ )] && Remove
", " add " )"
RETURN Cmd
ENDFUNC
FUNCTION <span
class="docEmphStrong">BuildUpdateCommand</span>
PARAMETERS pTable, pKeyField
FOR I = 1 TO FCOUNT()
Fld = UPPER(FIELD(I))
IF Fld = UPPER(pKeyField)
LOOP
ENDIF
Dta = []
Dta = [0]
ENDCASE
ENDIF
Dta = IIF ( Dta = [/ /], [], Dta ) Dta = IIF ( Dta = [.F.],
[0], Dta ) Dta = IIF ( Dta = [.T.], [1], Dta ) Dlm = IIF (
TYPE ( Fld ) $ [CM],['],; IIF ( TYPE ( Fld ) $ [DT],['],;
IIF ( TYPE ( Fld ) $ [IN],[], []))) Cmd = Cmd + Fld + [=]
+ Dlm + Dta + Dlm + [, ]
ENDFOR
Dlm = IIF ( TYPE ( pKeyField ) = [C], ['], [] ) Cmd =
LEFT ( Cmd, LEN(Cmd) -2 ) ;
&pExpr
THIS.GetHandle()
IF THIS.Handle < 1
RETURN
ENDIF
FUNCTION <span
class="docEmphStrong">GetNextKeyValue</span>
LPARAMETERS pTable
USE Keys IN 0
ENDIF
SELECT Keys
&Cmd
&Cmd
USE IN Keys
Now, select Change Data Source from the menu and enter
SQL, as shown in Figure 3.4.
In this chapter, we'll build a simple application framework for Visual Basic
.NET forms applications that use SQL Server. You'll see how object-
oriented techniques can greatly reduce development time and cost, and
simplify your job. If you thought Visual Basic .NET was a lot harder than
Visual FoxPro, I think you'll be pleasantly surprised.
A few forms to add, edit, and delete records from individual tables
You first have to pick a name for your new project. When you create this
project, Visual Studio adds a new directory under your default projects
directory, which is initially Documents and
Settings\MyUserID\Visual Studio Projects\YadaYada. I
changed mine to C:\VBProjects and recommend that you do likewise.
It creates both a solution (a container for several projects) and a project.
As you'll see repeatedly in our examples, Visual Basic .NET assumes a
different arrangement for projects than does FoxPro. In FoxPro, we build
one project, and may include several class libraries. In Visual Basic .NET,
each class library is usually built as its own project, and compiled as a
DLL, then included as a reference in other projects that use the classes.
It doesn't take long to get used to.
The newly created Visual Basic .NET Windows Application project also
includes one form, named Form1.vb by default. Form1 is both the
filename and an internal class name. You should change both. In this
case, because it's the first form that was created, we'll use it as we used
MAIN.PRG in our FoxPro project. There's no _Screen object in Visual
Basic .NET, so this first form will become our background screen. Using
F4, open the Properties window, and change the Name property to
AppScreen. (If you open the code window for the form, you'll see that
the class name in the first line has been changed to AppScreen.) Open
the Solution Explorer with Ctrl+Alt+L and select Rename, and change
Form1.vb to AppScreen.vb. Right-click again on the project in the
Solution Explorer and select Rebuild.
NOTE
Next, we'll need a menu. Use Ctrl+Alt+X to open the Toolbox, select
Windows Forms, and drag a MainMenu control to anywhere on the
AppScreen form. When selected, the MainMenu control appears in the
upper-left corner of the screen. For now, it's the only control, so it will be
selected automatically. Later, if you add other controls (for example, a
label) to the form, the menu control will disappear when the label is
selected. Click on the MainMenu control to begin building your menu.
StartPosition CenterScreen
FormBorderStyle Fixed3D
Remember that I said that some things are harder in .NET? This is one of
them: The drop-shadow trick that we used in Chapter 2, "Building Simple
Applications in Visual FoxPro and Visual Basic .NET," to put a title on the
screen with a "drop shadow" turns out to be unusually difficult in Visual
Basic .NET because the Label control can't be transparent on a
Windows form.
RANT
Imports System.Drawing
Public Class MyControls
Text = ""
Width = 200
IIf(Sender.enabled, BackColor.White, _
End Class
Text = ""
Width = 200
IIf(Sender.enabled, BackColor.White, _
End Class
Text = ""
Width = 200
IIf(Sender.enabled, BackColor.White,
System.Drawing.SystemColors.ControlLight) End Sub
End Class
Text = ""
Width = 200
End Class
Width = 200
End Class
Text = ""
End Sub
End Class
End Class
Finally, position all six of the buttons near the bottom left of the screen,
and then select all of them and set the Anchor property to Bottom,
Left, as shown in Figure 4.5.
I anchored the Show Matching Records button at the top and right, and
the list box is anchored at the top, right, and bottom. As a result,
automatic resizing performs exactly as you would expect. That's better
than writing all of that resizing code that used to be required, and it works
the same way in the IDE as it does in the final program.
The first thing I code is always the Close button because I'm anxious to
see it work. Double-click on the Close button and you'll see that the IDE
generates two lines of code. Listing 4.2 shows the code for the Click
event of the Close button. Note that the Handles clause determines
which event the routine responds to, not the routine name as is the case
in Visual FoxPro.
ByVal e As System.EventArgs) _
Handles btnClose.Click
Close()
End Sub
The Private Sub and End Sub lines were written by the IDE's code
generator. I added the Close command. (The ending parentheses were
added by the IDE.)
The form displays one record at a time, which makes saving changes
simple. That's the reason for this design. But that means we have to have
a mechanism for selecting a record for viewing or editing. That's what the
text box, the list box, and the Show Matching Record button are for. The
user enters a stringone or more lettersinto the text box and clicks on the
button, and the list is populated with all of the records that match. But
match on what? I've decided to specify a single search field, presumably
the most important field in the table, as the target for the search. I've also
decided to show all records that start with the string entered by the user.
As you'll see shortly, matching any part of the expression is equally easy.
But it's my design, so I'll do it my way.
To see how this works, compile the project. This creates a DLL named
BookInheritedForm.dll, which can contain several inheritable items.
Next, add a project called UseTheFormLuke to your current solution. (I
did my first FoxBASE project for George Lucas at the Skywalker Ranch.)
It will add a form called Form1, which we'll ignore for now.
Next, right-click on the new project and select Add, Add Inherited Form
from the context menu. The resulting dialog will first ask for a name for
the new form (call it Test), and then it will ask you to select from the
available inheritable classes, as shown in Figure 4.7. Select BaseForm,
the only one on the list. The resulting form will look just like BaseForm
because it inherits from BaseForm.
Next, drag a MainMenu control from the Windows Form toolbox to the
form's design surface. Type File in the top left cell, and Exit just below
it, as shown in Figure 4.8.
frm.Show()
Press F5 to compile and run your application. Then select Tables, Test
from the menu, and you'll see your first inherited form. It doesn't do much
yet, but it will.
Private _MainTable As String
Get
"Provider=SQLOLEDB;server=
(local);database=Northwind;uid=sa;pwd=;"
End Property
End Property
End Property
#End Region
Handles MyBase.Load
Try
dc = New OleDb.OleDbConnection
dc.ConnectionString = ConnStr dc.Open()
End Try
End Sub
Handles btnLoadList.Click
LoadTheList()
End Sub
+ SearchValue.Text.ToUpper.Trim + "%'"
.Items.Clear()
NumFound =
dsFiltered.Tables(MainTable).Rows.Count - 1
Next
End With
ListBox1.SelectedIndex = 0
LoadaRecord()
Buttons(TurnOn)
End Sub
Handles ListBox1.SelectedIndexChanged
LoadaRecord()
End Sub
Next
End Sub
Next
Next
End Sub
Handles btnAdd.Click
Try
dsOneRecord.Clear() BindingContext(dsOneRecord,
MainTable).AddNew() ClearFields()
Inputs(TurnOn)
Handles btnEdit.Click
Inputs(TurnOn)
End Sub
Handles btnDelete.Click
Try
BindingContext(dsOneRecord,
MainTable).RemoveAt(0) Dim cb As
OleDb.OleDbCommandBuilder cb = New
OleDb.OleDbCommandBuilder cb.DataAdapter =
daOneRecord daOneRecord.UpdateCommand =
cb.GetUpdateCommand()
daOneRecord.Update(dsOneRecord, MainTable)
dsOneRecord.Tables(MainTable).AcceptChanges()
LoadTheList()
Handles btnDelete.Click
Try
BindingContext(dsOneRecord,
MainTable).RemoveAt(0) Dim cb As
OleDb.OleDbCommandBuilder cb = New
OleDb.OleDbCommandBuilder cb.DataAdapter =
daOneRecord daOneRecord.UpdateCommand =
cb.GetUpdateCommand()
daOneRecord.Update(dsOneRecord, MainTable)
dsOneRecord.Tables(MainTable).AcceptChanges()
LoadTheList()
Handles btnSave.Click
Try
BindingContext(dsOneRecord,
MainTable).EndCurrentEdit() Dim FldName As String
Dim NewKey As String Dim Ctrl As Control For Each
Ctrl In Controls If TypeOf Ctrl Is TextBox And
Ctrl.Name <> "SearchValue" Then FldName =
Ctrl.Name.Substring(3) 'skip characters 0-2 - thanks,
Bill..
dsOneRecord.Tables(0).Rows(0).Item(FldName) =
Ctrl.Text If FldName = KeyField Then NewKey =
Ctrl.Text End If
End If
Next
Dim cb As OleDb.OleDbCommandBuilder cb = New
OleDb.OleDbCommandBuilder cb.DataAdapter =
daOneRecord daOneRecord.UpdateCommand =
cb.GetUpdateCommand()
daOneRecord.Update(dsOneRecord, MainTable)
dsOneRecord.Tables(MainTable).AcceptChanges() ' Load
the list so as to include the new record LoadTheList()
' Find the new key in the list Dim str As String Dim I
As Integer
For I = 0 To ListBox1.Items.Count - 1
str = ListBox1.Items(I) If
str.ToUpper.Substring(1).IndexOf(NewKey.ToUpper) > 0
Then ListBox1.SelectedIndex = I Exit For
End If
Next
LoadaRecord()
Inputs(TurnOff)
Handles btnCancel.Click
Try
BindingContext(dsOneRecord,
MainTable).CancelCurrentEdit() LoadaRecord()
Inputs(TurnOff)
Handles btnClose.Click
Close()
End Sub
That has changed. Without a doubt, the Internet is the biggest thing that's
ever happened to database application development. Using Internet-
enabled applications, users can access their data from anywhere that
has a telephone connection. With broadband access, the user
experience is almost identical to that of a local area network user.
ASP and Database Development
For several years, developers of Windows Forms database applications
were at a disadvantage when it came to building Internet-enabled
database apps. Active Server Pages (ASP) was easy to learn, easy to
use, and free. That's a hard combination to beat.
But ASP and even its successor ASP.NET are not the only answer. In
fact, for most applications, they're not even a good answer. Browsers like
Internet Explorer and Netscape
Finally, besides being slower than their rich client cousins and costing
more to develop and maintain, browser applications send their contents
as plain text. That's no way to distribute the company's sensitive
information; somewhere, some hacker will intercept and read their
contents.
For all three of these reasons, many developers feel that thin client
application development has been hugely oversold. There are several
reasons that come to mind.
You can't draw a picture of a car and drive it away, but building a Web
page that sort of works is pretty easy. However, it will seldom duplicate
the performance of an executable. User complaints don't begin until a
poorly designed system is deployed, and by then it's too late. Windows
Forms applications with well-designed interfaces based on well-designed
databases provide the best combination of cost, performance, and
features.
Browser applications have their place, but in time users will come to
realize that it's a pretty small place. For everything else, there's rich
client.
Still, getting access to data has never been easy. Traditional Visual Basic
applications used recordsets internally, but recordsets aren't files, but
rather in-memory structures that can't easily be sent across the Internet.
XML solves that problem. XML files are text files that express all data
fields as strings delimited by tags that reflect the name of the field that
the data came from. An inline schema included at the beginning of the file
(or named internally and provided in another file) can describe how to
reconstruct the table, if it's needed. However, when sending a known
record structure to a known client, the table structures are usually known
in advance. So XML is a simple way to send data from a server to a client
and back again.
However, as you'll see in the examples shown in this chapter, you can
send DBFs and FPTs across the Internet, and they work great. When
zipped and encrypted, they provide data security in about one-seventh
the space required for the equivalent XML. I wonder why Microsoft didn't
decide to use them as the basis for Internet data interchange as a
complement to XML. You'll have to ask them.
With the inclusion of support for XML Web Services in Visual FoxPro 8,
FoxPro applications have begun to look like database applications written
in .NET languages. Data acquisition involves creating a proxy for the
remote Web Service, data translation to and from XML strings, and
learning about diffgrams. It's a little different, but the results are
spectacular.
In this chapter we'll add Internet access to the data tier developed in our
FoxPro application in Chapter 2, "Building Sample Applications in Visual
FoxPro and Visual Basic .NET,"and we'll add XML Web Services to our
Visual Basic .NET application developed in Chapter 3, "Building a Visual
FoxPro Application for SQL Server." I'm going to describe two methods of
building Internet access for FoxPro, one for use with Visual FoxPro 7 and
another for use with Visual FoxPro 8. Visual FoxPro 7 has less Web
Services support than subsequent versions, and not everyone has
upgraded.
* Program-ID..: MAIN.PRG
WITH _Screen
+
[Server=VAIO\VAIO;Database=Northwind;UID=sa;PWD
WITH _Screen
.RemoveObject ( [Title1] )
.RemoveObject ( [Title2] )
ENDWITH
DEFINE CLASS Title AS Label
Visible = .T.
BackStyle = 0
FontSize = 48
Height = 100
Width = 800
Left = 25
PROCEDURE ErrTrap
OnError = ON("Error")
ON ERROR
IF NOT FILE ( [ERRORS.DBF] )
USE ERRORS IN 0
ENDIF
SELECT Errors
_Screen.RemoveObject ( [Title2] )
_Screen.RemoveObject ( [Title1] )
ENDIF
CLOSE ALL
RELEASE ALL
CANCEL
ELSE
ON ERROR &OnError
ENDIF
I've included in the following snippet the code changes you'll need to
make in order to use Internet support:
-End-
An Internet server works exactly the same way, except that now the
program goes to the Internet server instead of to SQL Server for the data.
It also goes to the Internet server for table structures because that's how
cursors are built.
First let's start building the Internet server. Web Connection installs in a
directory structure named \wconnect, typically on the C: drive. If you
installed it on another drive, substitute the correct drive letter wherever
you see C:. Beneath \wconnect, there are six or seven directories
depending on the version. I'm using Web Connection version 4.25 for this
book, and at this time the directories are these:
Classes
Console
FoxCentral
HTML
Scripts
SoapSamples
Templates
Tools
Wwdemo
wwDevRegistry
wwIPStuff_Samples
wwReader
wwThreads
The shareware version doesn't install all of these; however, the important
ones are Classes and wwDemo. The main directory contains about 15
files, but the one you'll want to look at is called WCDEMOMAIN.PRG. Open
it up and take a look. About 225 lines down, you'll find a section that
looks like Listing 5.2. (I added the last two lines.)
You're required to write a process class that contains your own functions.
wwDemo.PRG is a sample process class that you can use as a pattern.
When a program needs data, it sends an HTTP call to the server using a
syntax like this:
http://www.lespinter.com/wconnect/wc.dll?
wwdemo~Function1~Param1
IF lnReturnCode <> 0
RETURN
ENDIF
The process class library is derived from Rick's wwProcess class. You
start with a small skeleton and then add your own functions and
procedures. wwDemo.PRG is a sample that ships with Web Connection to
show you what a process class is.
How do you set up one of these class libraries? One way is to copy
wwdemo.prg to another name, erase most of its contents, and start
writing your own using the original wwdemo as a guide. But a utility
program that comes with WebConnection will build your empty shell class
library for you. Run console.exe and follow the instructions. I named
mine MyDataServer, so pick a similarly descriptive name.
After you've added your class library, add two lines to wcDemoMain that
direct URLs that reference your process class to the corresponding
object by adding the two lines of code shown in Listing 5.2, and you're
ready to start writing functions.
I've found two peculiarities in using Web Connection that might also give
you a few anxious moments: First, before typing DO WCDEMOMAIN, you
need to type the following in the command window:
lcEverything = Request.QueryString()
lcName = request.QueryString("Name")
lcName = request.QueryString(3)
FUNCTION GetOnerecord
&Cmd
USE IN ( pTable )
Response.Write ( lcXML )
ENDFUNC
oIP.AddPostKey ( Value, "key" )
lnBufLen = 0
IF _Tally > 0
SCAN
.AddListItem ( Company ) Row = .NewItemID
.Selected[1] = .T.
THISFORM.cmdSelect.Enabled = .T.
ELSE
THISFORM.cmdSelect.Enabled = .F.
ENDIF
ENDWITH
ENDPROC
PROCEDURE cmdall.Click
SELECT Carrier
ZAP
THISFORM.List1.Clear
lnBufLen = 0
.Selected[1] = .T.
ENDWITH
ENDPROC
lcXML = oXML.CursorToXML()
oXML.XMLtoCursor ( lcXML )
PROCEDURE Load
COMPANY Char(40), ;
CONTACT Char(30), ;
TITLE Char(30), ;
ADDRESS Char(60), ;
CITY Char(15), ;
REGION Char(15), ;
POSTALCODE Char(10), ;
COUNTRY Char(15), ;
PHONE Char(24), ;
FAX Char(24), ;
MAXORDAMT Y )
ENDPROC
PROCEDURE cmdFind.Click
WITH THISFORM
DO FORM GetCust TO CustKey && GetCust is a
MODAL form IF NOT EMPTY ( CustKey )
lnBufLen = 0
.cmdDelete.Enabled = .T.
ELSE
.cmdEdit.Enabled = .F.
.cmdDelete.Enabled = .F.
ENDIF
.cmdNext.Enabled = .T.
.cmdPrev.Enabled = .T.
ENDWITH
ENDPROC
PROCEDURE cmdAdd.Click
SELECT Customer
ZAP
DODEFAULT()
ENDPROC
LPARAMETER loServer
LOCAL loProcess
#INCLUDE WCONNECT.H
loProcess=CREATE("wwDemo",loServer)
loProcess.lShowRequestData =
loServer.lShowRequestData IF
VARTYPE(loProcess)#"O"
RETURN .F.
ENDIF
loProcess.Process()
RETURN
#DEFINE HOMEPATH "/wconnect/"
********************************
********************************
cHTMLPagePath = ""
cDataPath = ""
*******************
* wwDemo :: Process
*******************
FUNCTION Process
Config = THIS.oServer.oConfig.owwDemo
DODEFAULT()
RETURN .T.
ENDFUNC
FUNCTION GetRecords
loXML = CREATE("wwXML")
IF lcAccess = "DBF"
&lcCmd
SQLDISCONNECT(0)
Response.ContentType="text/xml"
lcXML = loXML.CursorToXML("Records","Record") IF
USED ( "C1" )
USE IN C1
ENDIF
Response.Write( loXML.EncodeXML(lcXML) )
RELEASE loXML
ENDFUNC
FUNCTION GetStructure
IF lcDBF == ""
SELECT 0
USE C1
ERASE C1.*
Handle = SQLSTRINGCONNECT(THIS.Connstring)
IF Handle > 0
RELEASE loXML
ENDFUNC
FUNCTION GetMatchingRecords
lcTable = Request.QueryString(5)
lcEncode = Request.QueryString(6)
DO CASE
CASE lcAccess = [DBF]
FUNCTION GetOneRecord
lcKeyField = Request.QueryString(5)
lcKeyValue = Request.QueryString(6)
lcEncode = Request.QueryString(7)
DO CASE
CASE lcAccess = [DBF]
SELECT 0
FUNCTION DoesTableHaveAMemoField
HasMemo = .F.
FOR I = 1 TO FCOUNT()
HasMemo = .T.
RETURN HasMemo
PROCEDURE InsertOrUpdateRecord
lcTable = Request.Form("Table" )
&lcCmd
Handle = SQLSTRINGCONNECT(THIS.Connstring)
IF Handle > 0
PROCEDURE GetNextKeyValue
&Cmd
&Cmd
USE IN Keys
Response.Write ( lcResult )
RETURN
ENDIF
ENDIF
IF RECCOUNT([SQLResult]) = 0
Response.Write ( lcResult )
RETURN
ENDIF
ENDIF
Cmd = [SELECT LastKeyVal FROM Keys WHERE
TableName='] + lcTable + [']
IF lr < 0
Response.Write ( lcResult )
RETURN
ENDIF
ENDIF
IF RECCOUNT([SQLResult]) = 0
IF lr < 0
Response.Write ( lcResult )
RETURN
ENDIF
ENDIF
ENDIF
IF lr < 0
Response.Write ( lcResult )
RETURN
ENDIF
ENDIF
Cmd = [SELECT LastKeyVal FROM Keys WHERE
TableName='] + lcTable + [']
IF lr < 0
Response.Write ( lcResult )
RETURN
ENDIF
ENDIF
lcResult = TRANSFORM(SQLResult.LastKeyVal)
USE IN SQLResult Response.Write ( lcResult )
ENDCASE
ENDPROC
PROCEDURE DeleteRecord
&lcCmd
Handle = SQLSTRINGCONNECT(THIS.Connstring)
IF Handle > 0
loXML = CREATE("wwXML")
&Cmd
USE IN C1
USE IN ( lcTable )
ENDFUNC
ENDDEFINE
*EOC MyDataServer.PRG
Server = P400
Method = DBF
lstr = [uid=sa;pwd=;server=P400;] ;
+ [ driver={SQL Server};database=Customers;]
lh = SQLStringConnect ( lstr )
IF lh < 0
* Program-Id....: Send2DBF.PRG
PARAMETERS ;
Adding = .T.
BuildUpdateString()
OTHERWISE
* Stored Procedure - just execute it
ENDCASE
&CommandOrProc
RETURN [Ok]
PROCEDURE BuildInsertString
Str = IIF(EMPTY(&Fld),[''],[']+TTOC(&fld)+['])
ENDCASE
Str = Fld + [=] + Str
CommandOrProc=CommandOrProc+Str+IIF(I=FCOUNT
[],[,])
ENDFOR
CommandOrProc = CommandOrProc + [ ] +
WhereClause
ENDPROC
Cust_ID Integer IDENTITY(1,1)
AccessMethod = []
ConnectionString = ;
[Driver={SQL Server};Server=
(Local);Database=Northwind;UID=sa;PWD=;]
Handle = 0
MyServerURL = [localhost/]
Prefix = [wconnect/wc.dll?MydataServer~]
CASE AM = [DBF]
CASE AM = [XML]
IF THIS.AccessMethod = [SQL]
IF THIS.Handle > 0
RETURN
ENDIF
THIS.Handle = SQLSTRINGCONNECT(
THIS.ConnectionString ) IF THIS.Handle < 1
PROCEDURE CreateCursor
lnBufLen = 0
USE Carrier
THIS.FillCursor(pTable)
ELSE
Msg = [Unable to return records] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
SELECT ( pTable )
ZAP
APPEND FROM DBF( [SQLResult] ) USE IN
SQLResult
lnBufLen = 0
+ pTable + [~] ;
+ [EncodeDBF]
PROCEDURE GetMatchingRecordsForView
CreateView ( pTable )
ENDIF
pFields = IIF ( EMPTY ( pFields ), [*], pFields ) pExpr =
IIF ( EMPTY ( pExpr ), [], ;
&cExpr
SELECT ( ViewName )
THIS.FillCursor(ViewName)
ELSE
Msg = [Unable to return records] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
CASE THIS.AccessMethod = [WC]
lnBufLen = 0
+ pTable + [~] ;
+ [EncodeDBF]
PROCEDURE CreateView
LPARAMETERS pTable
SELECT ( pTable )
AFIELDS( laFlds )
SELECT 0
SELECT ( pTable )
&cExpr
THIS.FillCursor( pTable )
ELSE
Msg = [Unable to return record] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
CASE THIS.AccessMethod = [WC]
lnBufLen = 0
+ pKeyField + [~] ;
ERASE SQLResult.DBF
GO TOP
CASE THIS.AccessMethod = [XML]
ENDCASE
ENDFUNC
PROCEDURE FillCursor
LPARAMETERS pTable
IF THIS.AccessMethod = [DBF]
RETURN
ENDIF
SELECT ( pTable )
ZAP
APPEND FROM DBF ( [SQLResult] )
USE IN SQLResult
GO TOP
ENDPROC
PROCEDURE DeleteRecord
lnBufLen = 0
+ pKeyField + [~] ;
MESSAGEBOX( lcBuffer )
ENDIF
CASE THIS.AccessMethod = [XML]
ENDCASE
ENDFUNC
PROCEDURE SaveRecord
IF THIS.AccessMethod = [DBF]
RETURN
ENDIF
IF pAdding
PROCEDURE InsertRecord
lnBufLen = 0
PROCEDURE UpdateRecord
lnBufLen = 0
FUNCTION BuildInsertCommand
FOR I = 1 TO FCOUNT()
FOR I = 1 TO FCOUNT()
Fld = FIELD(I)
Dta = IIF ( Dta = [/ /], [], Dta ) Dta = IIF ( Dta = [.F.],
[0], Dta ) Dta = IIF ( Dta = [.T.], [1], Dta ) Dlm = IIF (
TYPE ( Fld ) $ [CM],['],; IIF ( TYPE ( Fld ) $ [DT],['],;
IIF ( TYPE ( Fld ) $ [IN],[], []))) IF (
THIS.AccessMethod = [DBF] ) ; OR (
THIS.AccessMethod = [WC] AND THIS.SvrDataAccess
= [DBF] ) LDM = IIF ( TYPE ( Fld ) $ [DT], [{], Dlm )
RDM = IIF ( TYPE ( Fld ) $ [DT], [}], Dlm )
ELSE
LDM = Dlm
RDM = Dlm
ENDIF
Cmd = Cmd + LDM + Dta + RDM + [, ]
ENDFOR
Cmd = LEFT ( Cmd, LEN(Cmd) -2) + [ )] && Remove
", " add " )"
RETURN Cmd
ENDFUNC
FUNCTION BuildUpdateCommand
FOR I = 1 TO FCOUNT()
Dta = []
Dta = [0]
ENDCASE
ENDIF
Dta = IIF ( Dta = [/ /], [], Dta ) Dta = IIF ( Dta = [.F.],
[0], Dta ) Dta = IIF ( Dta = [.T.], [1], Dta ) Dlm = IIF (
TYPE ( Fld ) $ [CM],['],; IIF ( TYPE ( Fld ) $ [DT],['],;
IIF ( TYPE ( Fld ) $ [IN],[], []))) IF (
THIS.AccessMethod = [DBF] ) ; OR (
THIS.AccessMethod = [WC] AND THIS.SvrDataAccess
= [DBF] ) LDM = IIF ( TYPE ( Fld ) $ [DT], [{], Dlm )
RDM = IIF ( TYPE ( Fld ) $ [DT], [}], Dlm )
ELSE
LDM = Dlm
RDM = Dlm
ENDIF
Cmd = Cmd + Fld + [=] + LDM + Dta + RDM + [, ]
ENDFOR
Dlm = IIF ( TYPE ( pKeyField ) = [C], ['], [] ) Cmd =
LEFT ( Cmd, LEN(Cmd) -2 ) ;
LPARAMETERS pExpr
DO CASE
CASE THIS.AccessMethod = [DBF]
&pExpr
lnBufLen = 0
oIP.AddPostKey ( "ServerAccess",
THIS.SvrDataAccess ) oIP.AddPostKey ( "Expr", pExpr )
oIP.HTTPGetEx ( Cmd, @lcBuffer, @lnBufLen ) lcXML
= ALLTRIM( lcBuffer ) XMLTOCURSOR ( lcXML,
"SQLResult" )
GO TOP
BROWSE
FUNCTION GetNextKeyValue
LPARAMETERS pTable
&Cmd
&Cmd
USE IN Keys
lnBufLen = 0
ConnectionString = ;
[Driver={SQL Server};Server=
(local);Database=Northwind;UID=sa;PWD=;]
Handle = SQLSTRINGCONNECT(
THIS.ConnectionString ) lr = SQLEXEC( Handle, Cmd,
"Customers" )
LOCAL xa AS XMLAdapter
xa = CREA ( "XMLAdapter" )
WITH xa
.AddTableSchema("Customers") .ToXML("lcXML")
ENDWITH
xa = NULL
RELEASE xa
USE IN Customers
RETURN lcXML
ENDFUNC
ENDDEFINE
o = CREATEOBJECT ( "FoxServer.FoxClass" )
? o.AllCustomers()
<xsd:schema id="VFPDataSet"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn <img border="0" width="14"
height="9" align="left" src="images/ccc.gif"
alt="graphics/ccc.gif" />:schemas-microsoft-com:xml-
msdata"> <xsd:element name="VFPDataSet"
msdata:IsDataSet="true"> <xsd:complexType>
<xsd:choice maxOccurs="unbounded"> <xsd:element
name="Customers" minOccurs="0"
maxOccurs="unbounded"> <xsd:complexType>
<xsd:sequence> <xsd:element name="companyname">
<xsd:simpleType> <xsd:restriction base="xsd:string">
<xsd:maxLength value="40"/> </xsd:restriction>
</xsd:simpleType> </xsd:element> <xsd:element
name="customerid"> <xsd:simpleType> <xsd:restriction
base="xsd:string"> <xsd:maxLength value="5"/>
</xsd:restriction> </xsd:simpleType> </xsd:element>
</xsd:sequence> </xsd:complexType> </xsd:element>
</xsd:choice> <xsd:anyAttribute
namespace="http://www.w3.org/XML/1998/namespace"
<Customers>
! Regsvr32 FoxServer..dll
lcErrorMsg="Error: " +
TRANSFORM(loException.Errorno) ; + " -
"+loException.Message
DO CASE
CASE VARTYPE(loFoxClass)#"O"
CASE !EMPTY(loFoxClass.FaultCode)
lcErrorMsg=lcErrorMsg+CHR(13)+loFoxClass.Detail
OTHERWISE
ENDCASE
MESSAGEBOX(lcErrorMsg)
FINALLY
ENDTRY
Handle = SQLSTRINGCONNECT(
THIS.ConnectionString ) lr = SQLEXEC( Handle, Cmd,
"Customers" )
LOCAL xa AS XMLAdapter
xa = CREA ( "XMLAdapter" )
WITH xa
.AddTableSchema("Customers") .ToXML("lcXML")
ENDWITH
xa = NULL
RELEASE xa
USE IN Customers
RETURN lcXML
ENDFUNC
FUNCTION SPT ( Cmd as String ) AS String
Handle = SQLSTRINGCONNECT(
THIS.ConnectionString ) lr = SQLEXEC( Handle, Cmd,
"Customers" )
Figure 5.4. The Main form for the Web service-based Visual
Basic application.
When you use a local data source, you can add a data adapter and
create a dataset from it. But when you're using Web Services, you need
to create the Web service first, add a Web reference, and then base the
dataset definition on the Web reference. So before going any further, we'll
create the Web service.
This creates a parameterized query, which will return one record. You can
name the new DataAdapter daOneCustomer and the dataset
dsOneCustomer. You'll be surprised how easy it is to send a parameter
to a Web service and retrieve the selected record.
You'll need to open the Config.Web file in the project and add the
following line immediately after the <system.web> tag:
<appSettings>
</appSettings>
This will allow us to read the connection string from a text file that can
easily be changed when we deploy the application. We'll add that feature
near the end of this topic.
Now you're ready to build our Web service. We'll need a function to
populate the grid in the main application screen. That's what the
dsAllCustomers dataset was for. When the user selects a record to
edit, the second dataset, dsOneCustomer, will be used to return it. The
Delete command button will just pass the key of the record to be deleted,
to demonstrate the flexibility of Web services. They're called XML Web
Services, but you don't have to pass XML back and forth. XML is just the
transport mechanism for the parameters and results. You can send rows
and columns back as XML, but scalars (strings, integers, dates, and the
like) are just scalars.
Double-click on the design surface to open the code window, and enter
the code shown in Listing 5.20.
As dsAllCustomers
SqlDataAdapter1.Fill(AllCustomers)
Return AllCustomers
End Function
As dsOneCustomer
SqlDataAdapter2.SelectCommand.Parameters(0).Value =
SqlDataAdapter2.Fill(OneCustomer)
Return OneCustomer
End Function
As dsOneCustomer
SqlDataAdapter2.Update(customerChanges)
Return customerChanges
Else
Return Nothing
End If
End Function
Cn.ConnectionString = ConfigurationSettings.AppSett
cn.Open()
sc.ExecuteNonQuery()
cn.Close()
End Sub
How It Works
This lets you pick a Web service from your development machine or from
any computer on the Internet. Select the first link, Web Services on the
Local Machine, and you'll be able to pick your new Web service from the
Web services that you've built on your development computer, as shown
in Figure 5.6. This screen is a welcome addition to Visual Studio 2003.
The first release of Visual Studio required you to remember the name of
your WSDL file, and it's easy to forget when you're building half a dozen
of them a day.
ByVal e As System.EventArgs) _
Handles MyBase.Load
LoadData()
End Sub
SuspendLayout()
ws.Credentials = System.Net.CredentialCache.Defaul
DsAllCustomers1.Clear()
DsAllCustomers1.Merge(ws.GetAllCustomers())
DataGrid1.DataSource = DsAllCustomers1
DataGrid1.DataMember = "Customers"
ResumeLayout(False)
End Sub
ByVal e As System.EventArgs) _
Handles cmdAdd.Click
frm.RecordKey = "Add"
frm.ShowDialog()
LoadData()
End Sub
ByVal e As System.EventArgs) _
Handles cmdEdit.Click
DsAllCustomers1.Tables(0).Rows(DataGrid1.CurrentR
frm.RecordKey = Recordkey
frm.ShowDialog()
LoadData()
End Sub
ByVal e As System.EventArgs) _
Handles cmdDelete.Click
DsAllCustomers1.Tables(0).Rows(DataGrid1.CurrentR
ws.Credentials = System.Net.CredentialCache.Defaul
ws.DeleteOneCustomer(Recordkey)
LoadData()
End Sub
ByVal e As System.EventArgs) _
Handles cmdClose.Click
End
End Sub
How It Works
The form's Load event calls the LoadData method to get the entire
Customers table, which is returned as an XML dataset. I call this routine
after any sort of change (add, edit, or delete) to the source table, so the
routine clears the dataset, creates a proxy to host the Web service's
methods, authenticates the proxy (that's what that modification to
config.web was for), calls the GetAllCustomers method, and uses
the Merge method of the proxy to dump the records into the dataset.
When the user clicks on either Add or Edit, we create an instance of the
frmEditCustomer form class, which you'll see in the next section. In
the case of an Edit, we pass the value of the key for the currently
selected record in the grid, which is returned by the expression
DsAllCustomers1.Tables(0).Rows(DataGrid1.CurrentRowInd
That (0) out at the end means "column 1", for reasons that were the
subject of at least one rant in an earlier chapter. This is perhaps the
oddest syntax that I've come across in .NET. But it returns the key of the
record to edit. Passing the string "Add" instead will be handled by code in
the Customer Edit form, which you'll see at the end of this section. In
both cases, we use the instance's ShowDialog() method, which is like
setting a FoxPro form's WindowType = 1 Modal and activating the
form. The next line, LoadData(), executes when control returns from
the modal form.
Delete is accomplished simply by using the value of the key field from the
current record to call the Web service proxy's DeleteOneRecord()
method. I did it a little differently just to demonstrate how Web service
functions act almost exactly like methods on local classes. They can't
operate on global variables, but they can pass parameters back and
forth; and the parameters can be tables, in the form of datasets.
The code shown in Listing 5.22 either finds the record to edit and
displays it on the screen, or adds a blank record and inserts the first five
characters of a GUID (pronounced as two syllables, GOO-id) converted
to uppercase.
Get
Return _RecordKey
End Get
_RecordKey = Value
DsOneCustomer1.Clear()
Dim dr As DataRow
dr = DsOneCustomer1.Tables(0).NewRow
Dim I As Integer
For I = 0 To dr.ItemArray.Length - 1
dr(I) = ""
Next
dr(0) = Guid.NewGuid.ToString.Substring(0,
DsOneCustomer1.Tables(0).Rows.Add(dr)
Else
Dim ws As New UseWS.localhost.Chapter5WebSe
ws.Credentials = System.Net.CredentialCache
DsOneCustomer1.Merge(ws.GetOneCustomer(_Rec
End If
End Set
End Property
ByVal e As System.EventArgs) _
Handles cmdSave.Click
BindingContext(DsOneCustomer1, "Customers").EndCur
If DsOneCustomer1.HasChanges Then
ws.Credentials = System.Net.CredentialCache.De
diffCustomers.Merge(DsOneCustomer1.GetChanges(
diffCustomers = ws.UpdateCustomers(diffCustome
DsOneCustomer1.Merge(diffCustomers)
DsOneCustomer1.AcceptChanges()
Close()
End If
End Sub
ByVal e As System.EventArgs) _
Handles cmdCancel.Click
Close()
End Sub
How It Works
If the record key is Add, it means that the user clicked on the Add button.
What follows looks like a lot of code, and compared to FoxPro, it is. But
you can't just APPEND BLANK and REFRESH in Visual Basic .NET. You
have to Clear the dataset, create a DataRow object based on one of the
dataset's rows using the NewRow() method, assign a blank string to all
of the fields (that was easy because all of the fields in this table are of
type character) so that they won't be DBNulls, and finally add the data
row to the empty dataset. If they passed a valid record key, we instead
call the Web server proxy's GetOneCustomer method and use the
resulting returned XML string to fill the dataset. Databinding takes care of
the rest.
You should be aware that there is another technology for using remote
data services with smart client applications. It's called remoting.
Remoting sends data in a more compact format, and has many more
options, than Web services. It's more complicated than XML Web
Services, although I've seen examples that are only 30 or 40 lines of
code.
If you're using DBFs, you probably figured out that you don't want to send
DBFs using Web Connection's Compression methods, because they
don't work with long field names. They require a DBC, and you can't send
a DBF that's part of a DBC without sending the DBC, DCX, and DTC files
as well. And if you think you can just keep all of your field names to 10
characters or less, go look at the field names your users have been
using. I've seen field names 30 characters long in SQL databases.
That's why our examples all have short field names. But in the real world
they don't, so you may have problems demonstrating the difference
between DBF and SQL access if your SQL tables have long field names.
It may look complicated, but it's really pretty easy to do. If you download
the source code, you can have it up and running in 30 minutes, and you
can probably adapt one of your own forms to use my library of functions
(with a few modifications) in an hour or so. And I should point out that the
full version of Web Connection contains hundreds of functions, a number
of which would make some of the code that I wrote here unnecessary. In
fact, there's a function to package a DBF and its associated FPT for
transmission in a single command. And there are many, many more.
Visual FoxPro 8 has great new features for building XML Web Services. I
hope this doesn't sound like an advertisement. I don't work for West Wind
and don't have any interest in the company. However, you may conclude
that even though you could save a few hours' pay by building Web
Services in Visual FoxPro 8, there's no better combination than FoxPro,
Web Connection, and XML for building database applications for the
Internet.
Chapter 6. Data Access
IN THIS CHAPTER
Database Containers
The answer is, as Shakespeare said of love, "not wisely but too well." It
does everything we want and more; but jeez, why did they do it that way?
FoxPro was built for connected data access, which is very simple. As
soon as you build a disconnected data access strategy, all of the
implications have to be dealt with. .NET, on the other hand, starts with
disconnected data access, so the problems have to be dealt with
immediately.
Let's look at data issues, first at Visual FoxPro, then at the Visual Basic
side. We'll look at the Data Application Blocks, hastily issued after the
release of Visual Studio .NET 1.1 to answer the alarmed requests for a
simpler way to access data. And we'll speculate (within the bounds of a
putative NDA) as to what might be coming from Redmond.
Data Access in Visual FoxPro Before Visual
FoxPro 8
In earlier versions of FoxPro, we had a few ways to access data: DBFs,
views, remote views, and SQL Pass-Through. The advent of the Internet
allowed us to add HTTP and remote data servers to that list, including
new HTTP data access to SQL Server.
Local tables with the extension .dbf are the best-known identifying
characteristic of FoxPro applications. The DBF, described in an earlier
chapter, is a disk file with a header of 512 bytes if part of a database or
256 bytes if a free table, followed by 32 bytes to describe each field in the
table, followed by fixed-length records preceded by a delete byte, which
contains an asterisk if it has been marked for deletion with the DELETE
command. (Tables that are members of a database have the name of the
database container file [DBC] stored in the additional 256 bytes of the
header.) Cursors (described later in this chapter) have the same format
as free tables because they can't be created as members of a database.
Part of the header information is stored in hexadecimal format, including
LSB (least-significant byte first) format, so that reading cursors is a skill in
itself. However, it is almost never necessary to do so, unless you're Paul
Heiser and you sell a tool to fix them when they get damaged.
Creating a Table
You can create a table either interactively or in code. Usually, when I'm
building a system, I simply type
CREATE CUSTOMER
Tables can also be created using the CREATE TABLE command, which
has this syntax:
StartDate Date, ;
Cost Numeric(6,2), ;
Expires Date, ;
CustomerID Char(10) )
Typically, you would use some data source, perhaps a table of possible
fields to be included in a particular survey, and then generate a string
containing the command. Then you would use macro expansion, (for
example, &Cmd) to execute the command and create the table.
USE ( TableName )
This command opens the named DBF in the current work area and
moves the record pointer to the first record. In VB, there is no current
select area as there is in FoxPro, and the position of the record pointer
within a dataset serves as a surrogate for RECNO() (record number). In
FoxPro, the current select area (SELECT()) and the current record
number (RECNO()) are managed by the FoxPro environment, so that we
can use them without resorting to the methods or properties of some data
handling class. They're just there.
This notion of a current work area is not without its problems, and we've
all experienced them. If you already have a table open, say in select area
1, USE ( TableName ) will close it and open the new table in select
area 1. So you usually use the following syntax to ensure that you
haven't just closed a table that you still needed:
USE ( TableName )
USE ( TableName ) IN 0
However, this doesn't select the new table; that requires a separate
SELECT command. So you would ordinarily use this:
USE CUSTOMERS IN 0
SELECT CUSTOMERS
Aliases
IF EMPTY ( FName )
RETURN
ELSE
SELECT TIMESHEET
ENDIF
Visual Basic .NET doesn't have the concept of a default alias. You can't
type Phone and assume that it will know what table to look in. In fact, it
won't look in any table.
TIP
Cursors
Cursors are temporary tables. You get a cursor when you issue a SQL
SELECT statement to retrieve records from a DBF. You also get a cursor
when you return records from SQL Server. And as we've seen, you can
use XMLToCursor() to build a cursor from an XML string.
There is also a CREATE CURSOR command, which looks exactly like the
CREATE TABLE command. It creates a temporary structure like that of a
DBF, typically with the extension .tmp, which you won't see unless you
use the DBF ( cursorname ) function to return the name. I've only
come up with one use for this, in conjunction with the APPEND FROM (
tablename ) command, which requires a table name rather than a
cursor name.
Cursors, unlike tables, can have long field names. This makes them
perfect for storing the contents of cursors returned from SQL Server
because few SQL database administrators limit themselves to 10-
character field names.
Supporting Cast
Table 6.1. Commands and Functions for Working with Open Tables
FIELD(n) Returns the name of the nth field in the current alias
TOP or GO TOP Moves to the first record in the current index order, or
to record 1 if no index is attached
BOTTOM or GO BOTTOM Moves to the last record in the current index order, or to
record (RECCOUNT()) if no index is attached
SCAN...ENDSCAN Executes the code between these two words once for
each record in the current alias
Buffering
After you make changes to a buffered table, you can't close the form
that's using it unless you call TableUpdate() or TableRevert().
That's why, in your form templates where you use buffering, you either
have to set ControlBox to False to eliminate the possibility of a user
trying to close a form with pending changes, or automatically call
TableUpdate() or TableRevert() if a form is closed after changes
have been made, depending on which you want to be the default
behavior. It's a little messy, but it's unavoidable when you begin using
buffering.
FoxPro has a record pointer, which tells it which is the current record.
When you use APPEND BLANK, FoxPro jumps to the end of the alias and
adds a blank record. However, it's not actually added until you use
TABLEUPDATE(), if buffering is enabled. If you use TABLEREVERT(),
where is the record pointer? The answer is, it's one record beyond the
number of records in the file. So you would have a RECCOUNT() of, say,
12, and a RECNO() of 13.
One of the consequences of this is that if you cancel an add, you have to
tell FoxPro what record you were pointing to before the APPEND. That's
why the FoxPro template in Chapter 2, "Building Simple Applications in
Visual FoxPro and Visual Basic .NET," had a property named
BeforeAdd, so that after a canceled add we could go back to the record
that was showing before the APPEND BLANK was issued.
Indexes
or simply
SET ORDER TO ( TagName )
After an index has been selected, a SEEK command for a value that's in
the index will find any matching record. Depending on the SET NEAR and
SET EXACT commands, the record pointer can be set to the next record
following the place where the record would have been had it existed, or
to the end of the alias.
Database Containers
So far we've talked about free tables, which are not contained in a
database. Many of the features of FoxPro tables and cursors require the
use of a database container (DBC), a sort of metatable containing
references to tables and their fields and indexes. It also holds
transactional information, stored procedures and triggers (including
generated relational integrity triggers), and the SELECT statements and
connection information needed to build local and remote views. You can't
use any of the advanced features without including a database container
in your project. And if you do, consider buying Doug Hennig's Stonefield
Database Toolkit (SDT) (www.Stonefield.com), which adds a ton of
additional functionality to the DBC.
The DBC file is itself a DBF. Its structure is shown in Listing 6.2.
1 OBJECTIDInteger 4 No
2 PARENTIDInteger 4 No
3 OBJECTTYPE Character 10 No
4 OBJECTNAME Character 128 No
5 PROPERTYMemo (binary) 4 No
6 CODEMemo (binary) 4 No
7 RIINFO Character 6 No
8 USERMemo4 No
** Total ** 165
When you create the database, five records are added, containing five
records with the following values in the ObjectName field:
1 1 Database Database
2 1 Database TransactionLog
3 1 Database StoredProceduresSource
4 1 Database StoredProceduresObject
5 1 Database StoredProceduresDependencies
Local Views
Remote Views
Remote views are the same as local views except that their data sources
are not FoxPro tables. The sources can be SQL Server, or any ODBC
data source. In fact, you can create a remote view to a FoxPro table
using an ODBC driver, although as Nixon once said, it would be wrong.
(He did it anyway, as some of us recall.) Remote views can also be
updateable.
At one time, many of us hoped that remote views would provide a way to
use SQL Server. We were wrong. Remote views work, but barely. So,
instead, we use SQL Pass-Through (SPT).
SQL Pass-Through
SQL is supported directly in FoxPro. The name is meant to imply that if
for some reason you don't want to use remote views, real men use SQL
Pass-Through (SPT). The reason for the clear distinction is that for
FoxPro people, having to build an INSERT or UPDATE string seemed
incredibly difficult. After all, if we use DBFs, there's nothing to do; leave
the record, or issue TableUpdate() if you used buffering, and the
change is permanent. Issue APPEND BLANK on an unbuffered DBF, and
you have inserted a blank record, a truly unnatural act in SQL.
As long as we're quoting Nixon, let me make one thing perfectly clear: To
add a record in SQL, you must create and send a complete, syntactically
correct INSERT statement to SQL, using SQL delimiters. SQL's syntax is
slightly different than that of FoxPro's twangy dialect, and SQL Server
makes no effort to understand its distant cousin. Similarly, the UPDATE
and DELETE commands must be expressed in their full glory.
SQL offers several things that DBFs don't provide. BACKUP and RESTORE
are easier in every way. User access control is built in, and it's virtually
impossible to damage SQL tables or index files. We've all had to build
our own mechanisms to deal with these three problems, although the
excellent Stonefield Database extensions written by Doug Hennig do a
great job in these areas and, therefore, are part of the essential FoxPro
toolkit.
When a backup is done, the log file is erased. The theory is that if it is
necessary to restore a database from the last good backup, the
transactions log that has accumulated since that backup can be used to
restore the database up to the moment that the database failed. It is also
possible to use the BACKUP command to simply erase the transactions
log, and in fact that's how it is often used.
SQL only returns a handle to a user when a valid user ID and password
are supplied. You can either provide a single user ID and password for all
users of your application, or issue one to each user. In either case, you
don't have to write a single line of code to manage database access.
That can be the single reason that justifies using SQL Server instead of
DBFs.
Finally, SQL Server supports indexes that are remarkably similar to those
in FoxPro; in fact, the indexing technology used by Microsoft in the latest
versions of SQL Server came from FoxPro, and uses the Rushmore
technology first developed years ago by the Fox Software team in Toledo.
FoxPro indexes can be corrupted; SQL Server indexes are almost
impossible to damage, and can be rebuilt automatically by SQL Server.
If any of these three features are important to your client, SQL Server is
well worth the additional cost. However, SQL requires knowing more than
is required to use DBFs. If you want to use SQL Server, you're going to
have to cozy up to the Query Analyzer and get to know it. Help is
available, from the Help, Transact-SQL Help menu pad. Transact-SQL is
the actual name of the command language used in SQL Server, just as it
was when they bought it from Sybase. (Did I mention that Microsoft
bought some of its products from other companies rather than developing
them in-house? Even Word.) A quick primer follows.
Provider={SQL Server};Server=(local);Database=Northwin
SQLExec()
As we saw in full detail in Chapter 2, you can't bind the controls on your
forms to the cursor returned by SQL because it breaks the data binding.
The ControlSource property of each control must be the name of an
alias and field that exist at the end of the Load event of the form. That's
why we create a cursor in the Load event, and then "pour" the data form
that the cursor returned either by a call to SQLEXEC() or by a call to a
Web service into the previously defined cursor.
It even comes with a pair of help files. The one called SQLDMO80.hlp is
of absolutely no use unless you're already a SQLDMO expert. The other
one, SQLDMO.CHM, is pretty cool and contains excellent examples. Look
for how-to articles on the Internet, or visit my Web site.
Table 6.2 lists a few FoxPro functions that are available for use with SQL
Server.
SQLMORERESULTS() Copies another result set to a Visual FoxPro cursor if more result
sets are available
TIP
TIP
SQL Server is a different technology. The data is kept elsewhere, and the
supposition is that you'll bring only the record or group of records that are
needed for the current form. In FoxPro, you get all of the records all the
time. The SQL equivalent of USE CUSTOMERS is SELECT * FROM
CUSTOMERS, which brings the entire table to each workstation. The
slowdown can be glacial. So unless you change the architecture of each
and every screen, performance will be awful, and your users will be
outraged.
The CursorAdapter class permits us to deal with data of any sort, from
DBFs to SQL Server to other ODBC data sources to XML Web Services,
in a single way. Two new builders ease the transition, although when you
know what's required, you may prefer to write the few required lines of
code yourself.
When you open the Data Environment of a form and right-click anywhere
on the design surface, the context menu will appear. It contains an option
to Add CursorAdapter. When you know what properties are required and
how to code them, you'll use this. For the moment, click on the Builder
option.
Note that the connection strings for ADO and for ODBC are different. The
ADO connection string to connect to the Northwind database is this:
Driver={SQL Server};Server=(local);Database=Northwind;
I'm going to specify customers from California for this screen, so I select
tab 2, Data Access, and enter the following SELECT command:
SELECT ;
Customers.CustomerID, ;
Customers.CompanyName, ;
Customers.ContactName, ;
Customers.Country, ;
Customers.Phone ;
FROM Customers ;
WHERE Region='CA'
The Schema edit box is instantly populated with the body of a CREATE
CURSOR command:
The Data Access page should look like the one shown in Figure 6.4.
Updates can either be done one row at a time, or as a batch for all
records in the cursor. The only issue is performance; if sending all
changes at once will cause an unacceptable delay, send one each time a
row changes. I've selected all fields and specified that CustomerID is
the key field, as shown in Figure 6.5.
Figure 6.5. Specifying the fields to update and the key.
FUNCTION BuildUpdateCommand
FOR I = 1 TO FCOUNT()
Fld = UPPER(FIELD(I))
IF Fld = UPPER(pKeyField)
LOOP
ENDIF
LOOP
ENDIF
IF Dta = [.NULL.]
DO CASE
Dta = []
CASE TYPE ( Fld ) $ [INL]
Dta = [0]
ENDCASE
ENDIF
ENDFOR
ENDFUNC
If you want to manually code the CursorAdapter class, it's not difficult.
Use the Properties sheet to see which properties were set by the builder:
Alias = [Customers]
DataSourceType = [ADO]
Name = [Customers]
Tables = [Customers]
Flags = 0
The code written by the builder and placed in the Init of the
DataEnvironment, shown in Listing 6.4, explains these three entries.
local llReturn
do case
This.AddProperty(' VFPSetup', 0)
This. VFPSetup = 0
return
endcase
llReturn = dodefault()
***<SelectCmd>
Customers.CustomerID, ;
Customers.CompanyName, ;
Customers.ContactName, ;
Customers.Country, ;
Customers.Phone ;
FROM Customers ;
WHERE region='CA'
endtext
***</SelectCmd>
***<KeyFieldList>
CUSTOMERID
endtext
***</KeyFieldList>
***<UpdateNameList>
***</UpdateNameList>
***<UpdatableFieldList>
endtext
***</UpdatableFieldList>
local loConnDataSource
set multilocks on
loConnDataSource = createobject('ADODB.Connection')
***<DataSource>
loConnDataSource.ConnectionString = ;
[Driver={SQL Server};Server=VAIO\VAIO;Database=Northw
***</DataSource>
loConnDataSource.Open()
This.DataSource = createobject('ADODB.RecordSet')
This.DataSource.CursorLocation = 3&& adUseClient
This.DataSource.ActiveConnection = loConnDataSource
if This. VFPSetup = 1
This. VFPSetup = 2
endif
return llReturn
This looks a little complicated, but it's not bad when you understand
what's happening. Note the enhancements to the TEXT TO
<propertyname> command that allow you to populate properties on the
fly.
If you use a cursor, you can use SELECT ( cursorname) and you're
there. But a CursorAdapter is a class, not a cursor. So how do you find
it?
oCA.CursorFill
NOTE
There are a number of properties that you can set to determine how the
CursorAdapter operates. As you look at them, you will be reminded of
the settings for remote views in FoxPro 7. That's because the
CursorAdapter is the replacement for remote views. Table 6.3 shows
the most important CursorAdapter properties.
Property Use
DataSource Where to get the data from; only valid for ADO or ODBC
DataSourceTypes.
Method Use
You can use the XMLAdapter to read XML and convert it to a FoxPro
cursor. You can also use it to create XML strings. Using an object
instantiated from an XMLAdapter, you can store one or more table
objects and describe XML tables as cursors. The XMLADapter is based
on MSXML 4.0 Service Pack 1 or later, which is installed in the "Install
Prerequisites" phase of the installation of Visual FoxPro 8.
Read XML and related schemas from an XML source using the
LOADXML or ATTACH methods
Reading an XDR
<xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C1488
xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882
xmlns:rs='urn:schemas-microsoft-com:rowset'
xmlns:z='#RowsetSchema'>
<s:Schema id='RowsetSchema'>
rs:writeunknown='true' rs:nullable='true'>
</s:AttributeType>
<s:extends type='rs:rowbase'/>
</s:ElementType>
</s:Schema>
<rs:data>
<z:row xmlfield='12.12'/>
</rs:data>
</xml>
ENDTEXT
CLEAR
oXMLAdapter = NEWOBJECT('XMLAdapter')
oXMLadapter.LoadXML(cXML)
IF oXMLAdapter.Tables.Item(1).Fields.Item(1).DataType
? 'Failed'
ELSE
oXMLAdapter.Tables.Item(1).ToCursor()
oXMLAdapter.XMLNamespace=""
oXMLAdapter.ReleaseXML(.F.)
oXMLAdapter.XMLSchemaLocation='c:\myxmlfile.xsd'
oXMLAdapter.ToXML('c:\myxmlfile.xml',,.T.)
oXMLadapter2 = NEWOBJECT('xmladapter')
oXMLAdapter2.XMLSchemaLocation='c:\myxmlfile.xsd'
oXMLAdapter2.LoadXML('c:\myxmlfile.xml',.T.,.T.)
ENDIF
otable.ToCursor(.F.,"ABC")
loXMLHTTP.Open([GET], [http://localhost/Fox/GetClientR
loXMLHTTP.Send()
XMLTOCURSOR (loXMLHTTP.ResponseBody)
CursorSetProp([Buffering],5)
This is where the user changes the data. I've used a browse, although a
nice edit screen would accomplish the same goal. Pressing Ctrl+W ends
the editing phase, and the program continues (see Listing 6.8).
oXML.AddTableSchema ( "Clientes" )
oXML.IsDiffGram = .T.
oXML.UTF8Encoded = .T.
oXML.ToXML ( "lcXML", "", .F., .T., .T. )
loXMLHTTP.AddPostVar ( lcXML )
loXMLHTTP.Open([POST], [http://localhost/Fox/Actualiza
loXMLHTTP.Send()
At this point, we'll assume that the server has received the contents of
the post buffer. You could use an XML Web Service written in FoxPro, or
a WebConnection server. In either case, assume that you're now in the
server code shown in Listing 6.9.
NOTE
oXML.LoadXML ( lcXML )
oCA.DataSource = "Native"
oCA.CursorAttach ( "Clients" )
oTable = oXML.Tables.Item(1)
oField = oTable.Fields.Item(1)
oField.KeyField = .T.
CLOSE TABLES
CLOSE DATABASE
Of course, you can update a cursor here, then pass the changes on to
SQL Server. I personally believe that the combination of a FoxPro middle
tier and a SQL Server database is the best way to build Internet-enabled
database applications. But if the cost of licensing by the seat is an issue,
there are solutions for everyone. Don't tell Microsoft; it might reduce their
enthusiasm for FoxPro.
USE customers
loxml.addTableSchema("Customers")
loxml.ToXML("lcxml")
MESSAGEBOX( lcxml )
USE IN States
USE IN XStates
RETURN lcXML
ENDFUNC
ENDDEFINE
Compile it as a multithreaded DLL. Then, open the Task Pane and select
Publish Your XML Web Service. Click on the button with the three little
dots and select the DLL you just created. The dialog should look like
Figure 6.6.
Click on the Advanced button and verify that the only checked methods
are the ones you want to expose, and then click on the Generate button.
You should see the message box shown in Figure 6.7.
? oWS.StateList
<VFPData>
<xstates>
<stateabbr>AL</stateabbr>
<statename>Alabama</statename>
</xstates>
<xstates>
<stateabbr>CA</stateabbr>
<statename>California</statename>
</xstates>
<xstates>
<stateabbr>DE</stateabbr>
<statename>Delaware</statename>
</xstates>
<xstates>
</VFPData>
*_VFPWSDef:loWebClass=http://localhost/WebClass.wsdl,W
TRY
loWSHandler = NEWOBJECT("WSHandler",IIF(VERSION(2)
HOME()+"FFC\")+"_ws3client.v
loWebClass = loWSHandler.SetupClient("http://local
"WebClass", "WebClassSoapPort"
CATCH TO loException
lcErrorMsg="Error: "+TRANSFORM(loException.Errorno
DO CASE
CASE VARTYPE(loWebClass)#"O"
* Handle SOAP error connecting to web service
CASE !EMPTY(loWebClass.FaultCode)
lcErrorMsg=lcErrorMsg+CHR(13)+loWebClass.Detai
OTHERWISE
ENDCASE
MESSAGEBOX(lcErrorMsg)
FINALLY
ENDTRY
Press Ctrl+E, and the form runs as advertised. You can use an
XMLAdapter to move this data to a cursor, then save changes as a
diffgram and send the diffgram back to another function in the Web
service, which uses the code shown there to update the original data
table.
For me, the ability to quickly and easily build FoxPro Web services is the
biggest improvement in version 8.
In Visual Basic .NET, all recordsets are disconnected. You must create a
cursor, bring the data into the cursor, and show it to the user. After any
changes are made, you construct a command to send the data back to
the source from whence it came. This is the case regardless of the data
source. There are no discounts for local tables in Visual Basic .NET.
Visual Basic .NET uses datasets in the same way that we use cursors in
FoxPro. You create a dataset, then call the Fill method of the
associated DataAdapter to pour the data into the dataset. After any
changes, you call a CommandBuilder object to create an UPDATE,
INSERT, or DELETE command to be sent to the driver that actually
updates the data source. It's the only way it works.
Connections
Connections are strings that tell the driver how to connect to a data
source. Data sources can be OLEDB or ODBC, or they can be managed
providers. Managed means "written in .NET," and for this reason they are
more efficient (faster) than their OLEDB counterparts. ODBC drivers are
just dreadful and are usually used only if there's nothing else available.
There's a pretty good third-party market in ODBC drivers, and if you must
use a data source supported by ODBC, you should look into them.
Microsoft gives away several ODBC drivers for free, and as you might
expect, their price is a good indication of their value.
A connection string to the Northwind database on SQL Server accessed
from FoxPro was shown earlier in Listing 6.4. It's the same for Visual
Basic .NET:
Driver={SQL Server};Server=VAIO\VAIO;Database=Northwin
However, you can also register a SQL Server database as an ODBC data
source by running ODBCAD32.EXE from the command line or by clicking
on the ODBC Data Sources icon in the Control Panel. The resulting
dialog lets you select the driver, specify the database, and optionally
provide a user ID and password. If the user ID and password aren't
supplied in the connection definition, the user will have to specify them
every time a connection is made. Experiment with this a few times and
talk to whoever is paying for it before you decide how to proceed. It
makes a difference.
In the Visual Studio IDE, there is a Server Explorer. You can open it by
selecting View, Server Explorer from the menu, or by pressing Ctrl+Alt+S,
whether a project or solution is open or not. The Server Explorer is
shown in Figure 6.8.
Page 1 of the dialog is the Provider page. There are about 20 that have
been installed on my laptop by various product installations that I've
done. If you buy and install drivers from third parties, there will be a few
more. Notice that there is a Microsoft OLE DB Visual FoxPro driver, so if
you wondered whether Visual Basic .NET will support your FoxPro
tables, now you know. Note that the FoxPro driver option supports either
a DBC or a Free Tables Directory. Experiment with both and verify that
your choice does what you want it to do.
You can also add categories of users and give different rights to each,
although the granularity of SQL Server database access may not be
adequate for your requirements if you want to control access down to the
pageframe or field level. However, if your purpose is to keep users from
updating records that they're only supposed to read, producing an error
message when they click Save is a pretty blunt instrument compared to
simply disabling the Edit button to begin with if they don't have editing
rights. Users appreciate subtlety.
Notice on the third page of the Data Link Properties dialog that you can
limit access to read-only, read-write, read-write share, read-write
exclusive, or write-only. Again, these are probably more than you need in
most cases. The default Share Deny None is probably just what you
need. It supports optimistic buffering, which means that you're
responsible for ensuring that one user's changes don't overwrite
another's.
After you save a connection, you can drag and drop it onto a form, and it
will be used to connect to the data source. It's used either directly by a
command object or by a data adapter.
Data Adapters
Next, open the Visual Basic .NET IDE and select File, New Project from
the menu. Choose Visual Basic, Windows Application. Make sure that the
project is going where you want it to go, and note that the project name is
also the name of a new directory that Visual Studio is going to create for
you. Visual Studio will create a project with five standard .NET
namespace references needed to build a forms project: System,
System.Data, System.Drawing, System.Windows.Forms, and
System.XML. Remember that last one. It's there because forms need
data, and XML spells data in .NET. It also adds an AssemblyInfo.vb
file (like a project information file) and Form1.vb.
That first form is usually your Main form, which will contain your logo and
your application's main menu. However, you can also build an application
consisting of just a single form, and that's what we'll do here. Use F4 to
open the Properties window and change the Text property to "My
customers".
Next, open the Solution Explorer and add the connection you just created
by dragging and dropping it on the form. Visual Basic .NET will ask you if
you want to add a user ID and password. For now, ignore it.
SQL can use the SELECT statement and the unique index values to
generate the code for the INSERT, UPDATE, and DELETE commands.
However, if you only want a SELECT statement, click on the Advanced
Options button and uncheck the Generate Insert, Update and Delete
Statements check box. Also, if you don't want the code-generated SQL
statement to verify that no fields have been changed since the SELECT,
uncheck the Use Optimistic Concurrency check box. It improves
performance, and in many cases it's not necessary. You be the judge of
the probability that two people will try to change the same record on two
workstations at the same exact instant. It's usually infinitesimal.
This will cause the IDE to build a parameterized query. To execute it,
you'll need to supply a value for the parameter in code before executing
the Fill command:
OledbDataAdapter1.SelectCommand.Parameters(0).Value =
If this looks a lot like a local view in FoxPro, it should. When you open a
view in FoxPro, you're executing a SELECT command. When you close
the view, the view itself uses the SELECT command and the unique index
key to build and execute INSERT, UPDATE, and DELETE commands as
needed to update the source table. That's exactly how the DataAdapter
works. You just get to see all of the details.
Finally, add a grid to the form. Open the Toolbox using Ctrl+Alt+X, click
on the Windows Forms controls section heading, and drag a DataGrid
to the form. Open the Properties sheet, click on DataSource, and from
the pull-down list select dsCustomers1.Customers. You can also use
the Properties window to specify the dataset name dsCustomers1 as
the DataSource, and the table name Customers as the DataMember,
for the data grid.
But we're not done. We have to write some codeone line of code. Double-
click anywhere on the form except the grid, and the code window will open
with a first and last line of a Form_Load event wired to the Load event of
the form via a Handles clause. Type in the following line of code:
Me.OleDbDataAdapter1.Fill(DsCustomers1)
You don't need the "Me.", but it brings up IntelliSense and saves you
some typing. Use the Tab key to select the current suggestion and move
to the end of the selected text; or, use a period to do the same thing and
add a period at the end. The Fill command of the DataAdapter was
built when we constructed the SELECT command, and the
dsCustomers1 dataset was constructed from the DataAdapter's
SELECT command, so they're guaranteed to be conformable.
Press F5 to run the application. That's one line of code. In FoxPro, it also
takes one line of codea USE statement in the LOAD event of the form. So
far, it's a close race.
NOTE
I've had a few problems with the FoxPro OLE DB driver in
Visual Basic .NET. During the writing of this chapter, the
DataAdapter Configuration Wizard began inserting double
quotes around my table name, so that not even the SELECT
command would work. And many times it failed to generate
UPDATE and DELETE commands for unspecified reasons. I
had no such problem with the SQL Server or Access drivers.
You've probably already done this, but open up the code for the form.
You'll see a little box that contains the text
Click on the plus sign to the left of it, and scroll through the code. You'll
see a block of code declaring a series of objects named
SQLDataAdapter1, SQLSelectCommand1, SQLInsertCommand1,
SQLUpdateCommand1, SQLDeleteCommand1, and SQLConnection1.
Each of these is defined in the generated code, including the command
that SQL Server will need to do its magic. Each field is generated as a
parameter, which is filled in when the function is called. Listing 6.10 is an
example. The generated code produces long lines, so they look pretty
bad on the screen and on the page. Thankfully, we seldom look at this
code.
@" & _
@P" & _
gi" & _
gi" & _
a" & _
me" & _
na" & _
al" & _
@" & _
de" & _
al" & _
mp" & _
, " & _
That's one line of code. I really didn't need to see this. In Visual FoxPro,
we're blissfully unaware of what's needed to make things happen; Visual
Basic .NET shows you everything.
Datasets
To build a dataset, click on the Data tab in the Toolbox and drag an
OLEDBDataAdapter (or a SQLDataAdapter) to a form surface. There
are small differences, but essentially the SQLDataAdapter works only
with SQL Server, whereas the OLEDBDataAdapter works with SQL
Server plus many other data sources that have OLEDB drivers. The
SQLDataAdapter has less overhead, and is therefore faster and more
efficient.
XML is not a dataset, and a dataset is not a schema. And even though a
dataset can start with a schema, the first half-dozen lines of a dataset
and an XML schema file created from the same SQL table are quite
different. And XML is generic, while a dataset is a particular structure in
XML.
Typed Datasets
Use Ctrl+Alt+L to open the Solution Explorer, and then click on the Show
All Files icon at the top of the window. Notice that the dataset you just
created has a plus sign, indicating that there's more below it. Click on the
plus sign to expand the tree, and you'll see a file with the same name and
a .vb extension. Double-click on it to open the file. It's a Visual Basic
class that describes the tables in the dataset.
Use the View, Class View menu option to open the Class View window.
Select the DsCustomers.vb class that was generated automatically by
the Generate Dataset Wizard from the DataAdapter object, and you're
in for a shock. There are dozens and dozens of properties, events, and
methods in the generated code.
Most of them are prototypes; that is, you can add code wherever you
want to. For example, for each field there's a SetFaxNull subroutine.
The Customers table has a Fax column. In the database, Nulls are
permitted in this column. VB hates nulls. In fact, it will crash if you return
one. So, the typed dataset has provided a routine where you can write
code to specify what you want to return as a value if you add a record
and don't specify a value for the Fax column. The generated routine
inserts DBNull, but you might want to insert "N/A". The SetFaxNull
subroutine is where you would do so.
Go a little further and you begin to see how this works. The IDE has
generated a class definition called a CustomersDataTable, which
inherits from the .NET DataTable class. That means that it comes with
all of the properties, events, and methods of the DataTable class, which
you probably should learn about. The declaration is followed by a
declaration of a private variable called column&ColumnName (to use
FoxPro syntaxcolumnFax would be created for the Fax column in the
table, for example) as DataColumn for each of the columns in your
original data table. These are called fields, and for the first time, the
name makes sense in this context.
When I drive to the store to buy a loaf of bread, I'm exploding gasoline
thousands of times per minute. I know this is true, and in fact it would be
pretty dramatic to be down inside the engine watching all of it happen.
But I just want a loaf of bread, and I don't care about the exploding
gasoline.
I feel exactly, precisely the same way about all of this stuff. The less I
know about it, the better. All you need to know is that if you create a
dataset, you can bind the Text property of your onscreen controls to
Dataset.FieldName and it will work.
When you right-click on the Windows form project and select Add, Add
New Item, one of the options is Data Form Wizard, as shown in Figure
6.11.
When you select Data Form Wizard, the screen shown in Figure 6.12
appears. Click on the Next button to proceed.
The code for this generated screen is 360 lines long. It returns all of the
records in the table from SQL, then uses the VB equivalent of SKIP to
move backward and forward through the tables. It also assumes that my
users can just start typing in any field whenever they want, and that
they'll know that they need to "update" before they can go to another
recordtheir wrist will be slapped with an error message if they don't. So it
has little to commend it as a database application. But it works, and it
only took a few seconds.
Handles btnCancel.Click
Me.BindingContext(objdsCustomers, "Customers").Cance
Me.objdsCustomers_PositionChanged()
End Sub
Handles btnDelete.Click
If (Me.BindingContext(objdsCustomers, "Customers").C
Me.BindingContext(objdsCustomers, "Customers").Rem
Me.BindingContext(objdsCustomers, "Customers").Pos
Me.objdsCustomers_PositionChanged()
End If
End Sub
Private Sub btnAdd_Click( _
Handles btnAdd.Click
Try
Me.BindingContext(objdsCustomers, "Customers").EndCu
Me.BindingContext(objdsCustomers, "Customers").AddNe
System.Windows.Forms.MessageBox.Show(eEndEdit.Messa
End Try
Me.objdsCustomers_PositionChanged()
End Sub
Handles btnNavFirst.Click
Me.BindingContext(objdsCustomers, "Customers").Posit
Me.objdsCustomers_PositionChanged()
End Sub
Handles btnLast.Click
Me.BindingContext(objdsCustomers, "Customers").Posit
(Me.objdsCustomers.Tables("Customers").Rows.Count - 1
Me.objdsCustomers_PositionChanged()
End Sub
Handles btnNavPrev.Click
Me.BindingContext(objdsCustomers, "Customers").Posit
(Me.BindingContext(objdsCustomers, "Customers").Posit
Me.objdsCustomers_PositionChanged()
End Sub
Private Sub btnNavNext_Click( _
Handles btnNavNext.Click
Me.BindingContext(objdsCustomers, "Customers").Posit
(Me.BindingContext(objdsCustomers, "Customers").Posit
Me.objdsCustomers_PositionChanged()
End Sub
Me.lblNavLocation.Text = (((Me.BindingContext(objdsC
"Customers").Position + 1).ToString _
End Sub
Handles btnCancelAll.Click
Me.objdsCustomers.RejectChanges()
End Sub
Me.BindingContext(objdsCustomers, "Customers").Positio
(Me.BindingContext(objdsCustomers, "Customers").Positi
SKIP
Me.BindingContext(objdsCustomers, "Customers").RemoveA
Me.BindingContext(objdsCustomers, "Customers").Positio
DELETE
Me.BindingContext(objdsCustomers, "Customers").AddNew(
APPEND BLANK
I'm starting to like Visual Basic .NET. But I still like FoxPro better.
Handles btnLoad.Click
c.ConnectionString = "Server=VAIO\VAIO;Database=Nort
c.Open()
da.Fill(Me.objdsCustomers, "Customers")
End Sub
Data Binding
In FoxPro, if you open the Data Environment, click on a cursor name, and
drag the word Fields onto the surface of the form, you get all of the fields
in the table, using your classes, with a label to the left of them with a
name for the field to the right of the label. The controls that are used are
your own subclassed controls from your own VCX file, based on your
settings on the Field Mappings page of the Tools, Options dialog. The
labels are the text stored in the corresponding records in the database
container, if you used one. The ControlSource property of each
control is automatically filled in, and when you open the form and
navigate through your data, a simple THISFORM.Refresh shows you
that the record pointer has moved.
Open up the form generated by the Data Form Wizard, right-click on any
of the text boxes, and look at the DataBindings property. You'll have to
click on the plus sign to expand the property and see its Text property.
TIP
Just to clarify, the Text property is the equivalent of the
Value property of a text box, combo box or check box, or
the caption property of a label.
I've opened the drop-down for the Text property so that you can see
how it has been populated, as shown in Figure 6.19.
Now you know that you can define a dataset, then drop controls on the
form and fill in the Text DataBindings property with individual fields
from the dataset, and it will work.
However, if you open up the code, you'll find this:
Me.editCustomerID.DataBindings.Add(New System.Windows.
Me.objdsCustomers, "Customers.CustomerID"))
It seems that data binding is actually the result of a command that we can
write ourselves. The syntax in pseudocode is this:
That's one of the reasons why we'll want to use typed datasets. If I can
loop through all of the controls on the form and bind them to my dataset
using names from a typed dataset, I don't have to go through dozens of
controls on dozens of forms clicking and selecting the field name for each
one. This is a good thing.
Typed datasets are the tip of the iceberg. At a minimum, a typed dataset
contains one property procedure for each column in the corresponding
schema, so that, for example, an expression like Customers.Name is
meaningful. Otherwise, unlike FoxPro, it has no intrinsic meaning in
Visual Studio 1.1.
Many developers have taken the notion of typed datasets far, far further.
By adding events that other objects can respond to, it's possible (for
example) to detect when data changes, look for "save" and "cancel"
command buttons on the form and enable them, without writing any screen-
specific code. The DataSet object itself does this. And many very
sophisticated extensions are possible. IdeaBlade from ObjectWare, for
example, can save data either back to the server, or to a local XML cache
for subsequent synchronization with the server whenever a connection can
be established. This is done using methods built into a generated DataSet
object. There's no limit to what they can be designed to do except our
imaginations.
Tables
Debug.WriteLine oTable.TableName
End For
Of course, the Table object itself has properties, events, and methods.
Some of them will be useful. So expect to see code like this:
followed by calls to methods on the oTable object. If you ever get used
to referring to table number zero as the first table, you've outdone me. It
just creeps me out.
Rows
By the same token, tables have rows. So we can also write this:
Dim I As Integer
For I = 0 To oRow.ItemArray.Length - 1
Debug.WriteLine(oRow.ItemArray(I))
Next
Debug.WriteLine(oTable.TableName)
Next
Next
Row objects give you access to their columns via overloading. You can
assign a value to the first column in a row, a column named CustomerID,
using either the column name or its number (starting with zero, of
course):
Row("CustomerID") = 1234
or
Row(0) = 1234
Columns
Each row contains a columns collection. The following code prints all of
the rows in a table:
Console.WriteLine(myRow(myColumn))
Next
Next
End Sub
XML Web services in .NET are as easy as they are in Visual FoxPro 8,
and a heck of a lot easier than they are in earlier versions of Fox. Create
a new project, and pick ASP.NET Web Services from the Visual Basic
New Project dialog, as shown in Figure 6.20.
'<WebMethod()> _
'End Function
The <WebMethod()> attribute prefix exposes your new Web service for
testing on a Web page test bed that's automatically generated by Visual
Studio. We'll need that, but everything else has got to go.
Now we're ready. Double-click on the form to open the code window.
Replace the entire sample function with this:
SqlDataAdapter1.Fill(Customers)
Return Customers
End Function
Press F5 to run this, and the screen shown in Figure 6.22 appears.
Figure 6.22. The generated test bed fir your XML Web
Service.
Visual Studio has created a test bed to test our Web service. This test
bed page will list all of your Web service's functions, and will allow you to
test any of them that have parameters that are primitive types and can,
therefore, be entered on a Web page for use in testing. Thus any calls
that require a dataset as input (such as an update routine) will be listed,
but not testable.
Open the Web service code again and add the following code:
As dsCustomers
SqlDataAdapter1.Update(CustChanges)
Return CustChanges
Else
Return Nothing
End If
End Function
We'll need a Windows form application to test this Web service, so add a
Windows Form project to the solution. Call it WinClientCustomers. On the
form, add a DataGrid and two buttons named LoadData and
SaveData.
To use this Web service, you need one method in your client form to get
the records, and a command button to extract any changed records from
the dataset and send them back. You'll need a dataset and a data
source. But in this case, it's coming from a Web service. We need to write
code that goes to the Internet for the data. And that's exactly where we
go for the data connection as well.
Right-click on the project and select Add Web Reference from the context
menu. You'll see the screen shown in Figure 6.23.
Click on the Web Services on Local Machine link. Pick your new Web
service from the resulting list. A Web References item will be added to
the WinClientCustomer project, right under References.
Listing 6.11 has the code for the Load event of the form:
ws.Credentials = System.Net.CredentialCache.Defaul
DsCustomers1.Merge(ws.GetCustomers())
End Sub
Listing 6.12 contains the code needed to save changes to the dataset.
If DsCustomers1.HasChanges() Then
Ws.Credentials = System.Net.CredentialCache.Def
diffCustomers.Merge(DsCustomers1.GetChanges())
diffCustomers = ws.UpdateCustomers(diffCustomer
DsCustomers1.Merge(diffCustomers)
End If
End Sub
The only thing remaining is to enter the DataSource for the grid. Open
the drop-down and select DsCustomers1.Customers, the typed
dataset. Now we're ready to give it a try.
Press F5 to run this application. This will bring up the screen shown in
Figure 6.24.
Change something and click the Update button, and then close the form
and run it again to see if the changes were saved. Worked on mine the
first time.
You can hardly beat a Web service that only takes 14 lines of code, and a
client form that acquires remote data and saves the changes back to the
data source in another 15 lines of code. That's less coding that it takes in
FoxPro.
Hierarchical XML
In this chapter you'll learn a lot more about XML, and gain an
understanding of its advantages in building database applications.
Schemas can come in several forms. The most basic is the DTD, which
looks like the example in Listing 7.1.
<?xml version="1.0"?>
<!DOCTYPE clientes [
The DTD is like a schema, like what you see in FoxPro if you type
USE (filename)
DISPLAY STRUCTURE
It lets you reconstruct the structure of the original table that was used to
build the XML file.
The DTD is followed by elements, which is what XML calls data, and the
element tags that bracket it. An element is like a field (a table column). It
consists of the name of the element between angle brackets (for
example, <name>), followed by the data value, followed by the element
tag with a forward slash before the element name (for example,
</name>). For example
<firstname>Les</firstname>
<firstname>Les</firstname>
<lastname>Pinter</lastname>
</record>
The table itself also has prefix and suffix tags. If an element is empty, a
single tag with a forward slash at the end indicates that the attribute is
empty, for example, <phone/> means that the phone field is empty. This
is not required; you can simply put beginning and ending tags (for
example, <phone></phone>) to describe an empty field. But the shorter
format occupies less space.
<currencies>
<currency name="" symbol="" display="Select..." />
...
<Currencies>
XML can also contain XML, because it's just data. But to prevent the XML
processor from trying to treat the embedded XML text as XML that needs
to be rendered (for example, if the text contains the string "<abc>"), the
strings can be enclosed in CDATA sections, which consist of the string "<!
[CDATA[", followed by the text, followed by "]]" to close the CDATA
section. This mechanism is commonly used in memo fields "just in case."
Encoding
The "encoding" notation in the XML prologue (the part of the ?XML line
that says encoding="UTF-8") is referred to as a transformation format,
and can be quite important, especially if you're using a language other
than English. Because there are many alphabets, you use the encoding
string to specify how the data is encoded.
UTF-8 is the most general. It uses one to six bytes to represent each
character. If you ever get a chance to see a Chinese word processor, you
can see the operator move down the narrowing tree of choices after each
byte until the final tier appears, and you'll have a good understanding of
how this works. But the browser just reads bytes until it completes a
character, then displays it. How does it know when it's at the end of a
character? Easy: The last byte of a character contains a zero in the high-
order bit, so the first byte with a hex value of 128 or less is the end of the
character. UTF-16 is Unicode, which uses two bytes for every character.
If a transformation format is used, the first two or three bytes of the XML
file are used as a Byte Order Mark (BOM) to indicate which code was
used to create the content. Documents encoded in UTF-8 start with a
BOM of 0xEF 0xBB 0xBF, and those encoded in UTF-16 begin with
0xEF 0Xff or 0Xff 0XFE. The browser knows.
Namespaces
<client>
<pinter:name>José</pinter:name>
<pinter:lastname>García</pinter:lastname>
</client>
</clients>
The only purpose of this is to permit you to qualify each entry with the
namespace and a colon, for example, Pinter:name instead of name.
This allows you to build XML files in which the same element name may
exist in several schema, by prefixing them with the alias for the
namespace. It doesn't have anything to do with your Web site. The only
reason you usually use your Web site is that there's only one, thanks to
Register.com and the DSN servers. I've never had a name conflict
requiring the use of namespaces to resolve conflicts, but the solution is
free, so you probably should use it.
To implement an XDR schema, you create a DTD file with the extension
.xdr and include a <schema></schema> section that describes the
elements by type (for example, integers and dates). Then you implement
it by including an "x-schema" specification, thusly:
<clients xmlns="x-schema:mixdr.xdr">
After you've done this, any attempt to load an XML file that doesn't
conform to the data definition included in the XDR file will fail. In addition
to specifying data types, you can specify minimum and maximum values
for certain fields, maximum string lengths, absence of required fields or
presence of forbidden ones, and other types of validations.
Later in this chapter we'll see how to create an XSD file in FoxPro and
use it to validate XML files. I've never used an XDR to enforce the
structure of an XML file. I generally have control over both ends of the
process, so I know for sure that the XML is what my program is
expecting. I suppose that a browser environment would be the most likely
one for using XDR validation. But it's another tool, and at least you'll
know what it is when you see it.
Examples of XML
CLIENTS.DBF:
ClientID Numeric ( 6)
<clients>
<client>
<clientid>104></clientid>
<company>Smith Electronics</company>
<phone>311-4032</phone>
<balance>189.00</balance>
</client>
<client>
<clientid>104></clientid>
<company>Family Clinic</company>
<phone>289-2904</phone>
<balance>225.25</balance>
</client>
</clients>
I say one representation because there are several ways to represent
this data as XML. For example, you can include a schema at the top of
the file, and it could be an XDR or an XSD representation.
You can include or exclude the carriage returns that visually separate the
elements and make the file easier for humans to read, although mattering
not at all to a parsing program. And you can represent fields as elements
or as attributes and thereby reduce the number of tags by half (because
the </ends> aren't required when using attributes), so long as the
receiving program knows what to look for.
FoxPro can read this kind of XML and turn it into a cursor (a temporary
DBF). Use the following syntax:
BROWSE
You can also open this file up and view it as a table in Visual Studio.
Select File, Open, File from the menu, and then select
Chapter7Code\Simple.XML, and you'll see the screen shown in
Figure 7.1. Select the Data tab at the lower-left corner of the screen to
display the table.
Sub Main()
oXML.Load("D:\6493SAMS\Chapter7Code\Simple.xml")
MsgBox(oXML.InnerXml)
End Sub
INVOICES.DBF:
InvNum Integer(4)
ClientID Integer(4)
Total Numeric(8,2)
INVDETL.DBF:
InvNum Integer(4)
LineNum Integer(4)
Quantity Integer(4)
ProductID Char(20)
UnitPrice Numeric(8,2)
Extended Numeric(8,2)
<invoice> <invnum_a>141</invnum_a>
<clientid>41</clientid> <date>2004-04-01</date>
<total>192.00</total> <invnum_b>141</invnum_b>
<linenum>1</linenum> <quantity>3</quantity>
<productid>303142-A</productid>
<unitprice>12.95</unitprice>
<extended>38.85</extended> </invoice> <invoice>
<invnum_a>141</invnum_a> <clientid>41</clientid>
<date>2004-04-01</date> <total>192.00</total>
<invnum_b>141</invnum_b> <linenum>2</linenum>
<quantity>4</quantity>
<productid>1041202</productid>
<unitprice>3.15</unitprice>
<extended>12.60</extended> </invoice> <invoice>
<invnum_a>142</invnum_a> <clientid>43</clientid>
<date>2004-04-03</date> <total>225.00</total>
<invnum_b>142</invnum_b> <linenum>1</linenum>
<quantity>1</quantity>
<productid>2022201</productid>
<unitprice>25.00</unitprice>
<extended>25.00</extended> </invoice> <invoice>
<invnum_a>142</invnum_a> <clientid>43</clientid>
<date>2004-04-03</date> <total>225.00</total>
<invnum_b>142</invnum_b> <linenum>2</linenum>
<quantity>2</quantity>
<productid>2016615</productid>
<unitprice>89.95</unitprice>
<extended>179.90</extended> </invoice> </invoices>
Add2XML ( [<Invoices>] )
NoFac = 0
SCAN
NoFac = NoFac + 1
NoLine = 0
SCAN
NoLine = NoLine + 1
Add2XML ( [</Invoice>], 1 )
ENDSCAN
Add2XML ( [</Invoices>] )
FUNCTION Add2XML
<Invoice> <invnum>141</invnum>
<clientid>41</clientid> <date>04/01/2004</date>
<total>192</total> <Line>
<invnum>141</invnum> <linenum>1</linenum>
<quantity>3</quantity> <productid>303142-
A</productid> <unitprice>12.95</unitprice>
<extended>38.85</extended> </Line>
<Line>
<invnum>141</invnum> <linenum>2</linenum>
<quantity>4</quantity>
<productid>1041202</productid>
<unitprice>3.15</unitprice>
<extended>12.60</extended> </Line>
<Line>
<invnum>142</invnum> <linenum>2</linenum>
<quantity>2</quantity>
<productid>2016615</productid>
<unitprice>89.95</unitprice>
<extended>179.90</extended> </Line>
</Invoice> </Invoices>
Open Visual Studio and select File, Open, File from the
menu to open Hierarchical.XML. You'll see the screen
shown in Figure 7.3.
loDOM = CREATEOBJECT
("MSXML2.DOMDocument.4.0" )
loDOM.Async = .F.
loDOM = CREATEOBJECT
("MSXML2.DOMDocument.4.0" ) loDOM.Async = .F.
loDOM.Load
("D:\6493SAMS\Chapter7Code\XMLWithErrors.xml")
IF loDOM.ParseError.ErrorCode <> 0
RELEASE loDOM
USE CUSTOMERS
LOCATE FOR CustomerID = [AROUT]
lcXML = []
<clients>
<client>
<companyname><<CompanyName>>
</companyname> <contactname><<ContactName>>
</contactname> </client>
</clients>
ENDTEXT
MESSAGEBOX( lcXML )
MESSAGEBOX( loXML.xml )
CursorToXML()
CursorToXML(nWorkArea | cTableAlias,
cOutput
[, nOutputFormat
[, nFlags
[, nRecords
[, cSchemaName
[, cSchemaLocation
[, cNameSpace ]]]]]])
The first parameter can be a work area number, although I always use an
alias. You can also use ALIAS() to use the currently selected table.
The third parameter is the output format of the result, and can be one of
three values:
1. Elements
2. Attributes
3. Raw (generic)
For a table containing fields Name, Counter, and Date, this is what the
XML would look like for these three formats:
Elements:
<VFPData>
<test>
<name>Pinter</name>
<counter>1234</counter>
<date>2004-03-12</date>
</test>
</VFPData>
Attributes:
<VFPData>
</VFPData>
Raw:
<VFPData>
</VFPData>
If we're producing XML for our own use, the first type, which is the
default, is okay. External users may need one of the other two formats.
The fourth parameter is the sum of the values of any of the parameters
listed in Table 7.1. This is analogous to the way you add values in the
MessageBox() function (for example, 4 + 32 + 256 means "Display a
question mark"; prompts include Yes and No, and default to No, but you
can just use 292).
Table 7.1. Valid Values of the Flags Parameter (the fourth parameter) of
the CursorToXML() Function
Value Description
1 Continuous string
2 Use beginning and ending tags for empty elements (that is, <a></a>
instead of <a/>)
For the following examples I'll assume a table named Clients containing a
single record.
Example 1:
This code will store the XML shown in Listing 7.11 in the variable
lcClients.
<VFPData>
<clients>
<first>Les</first>
<last>Pinter</last>
<phone>650-344-3969</phone>
</clients>
</VFPData>
Example 2:
CURSORTOXML ("CLIENTES", "CLIENTES.XML", 1, 512, 0, "1
This code will produce a table containing the results shown in Listing
7.12.
<VFPData>
:schemas-microsoft-com:xml-msdata">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="fir
<xsd:simpleType>
<xsd:restricti
<xsd:maxLe
</xsd:restrict
</xsd:simpleType>
</xsd:element>
<xsd:element name="las
<xsd:simpleType>
<xsd:restricti
<xsd:maxLe
</xsd:restrict
</xsd:simpleType>
</xsd:element>
<xsd:element name="pho
<xsd:simpleType>
<xsd:restricti
<xsd:maxLe
</xsd:restrict
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:anyAttribute namespace="http://ww
processContents="lax"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<clients>
<first>Les</first>
<last>Pinter</last>
<phone>650-344-3969</phone>
</clients>
</VFPData>
XMLToCursor()
The official syntax of the XMLToCursor() command is as follows:
[, cCursorName
[, nFlags ]])
The second parameter is the name of the cursor where the XML will be
converted to a table. If the cursor does not already exist, and if the XML
contains a schema, the cursor will be created using the schema
definition. If no schema definition is included, all fields will be created as
type Character, and the length of each field will be the length of the
longest string in each data element.
The third parameter is a flag whose value is the sum of any applicable
switches, as described in Table 7.2.
Value Description
8192 Don't modify the target table's structure (Visual FoxPro 8!).
The last flag is new in Visual FoxPro 8 and is most welcome. Previously,
table structures were always changed to reflect the longest string in each
field, and data types occasionally were changed as well. As a result, it
was generally necessary to write to a temporary cursor, then append the
data from it into the original structure, as shown in Listing 7.13.
<VFPData>
<clients>
<first>Les</first>
<last>Pinter</last>
<phone>650-344-3969</phone>
</clients>
</VFPData>
XMLToCursor(lcxml,"TEST")
DISP STRU
1 FIRST Character 3
2 LAST Character 6
3 PHONE Character 12
** Total ** 22
<VFPData>
:schemas-microsoft-com:xml-msdata">
<xsd:choice maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="first">
<xsd:simpleType>
<xsd:restriction base="xsd:string"
<xsd:maxLength value="15"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="last">
<xsd:simpleType>
<xsd:restriction base="xsd:string"
<xsd:maxLength value="15"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="phone">
<xsd:simpleType>
<xsd:restriction base="xsd:string"
<xsd:maxLength value="13"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:anyAttribute namespace="http://www.w3.org
processContents="lax"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<clients>
<first>Les</first>
<last>Pinter</last>
<phone>650-344-3969</phone>
</clients>
</VFPData>
You can use the XMLToCursor() function to convert this XML back into a
cursor, and DISPLAY Structure to view the resulting table:
Example: The following two lines of code produce the output that follows
the code:
XMLTOCURSOR(lcxml,"TEST")
1 FIRST Character 15
2 LAST Character 15
3 PHONE Character 13
** Total ** 44
MSXML2.DOMDocument.1.0
MSXML2.DOMDocument.2.0
MSXML2.DOMDocument.2.5
MSXML2.DOMDocument.2.6
MSXML2.DOMDocument.3.0
MSXML2.DOMDocument.4.0
loXML = CREATEOBJECT (
"MSXML2.DOMDocument.3.0" )
LOCAL RS AS "ADODOB.Recordset"
tempXML = GETFILE("XML")
SqlDataAdapter1.Fill(authors)
Return authors
End Function
And the code to receive the output from the Web service and convert it
back into a recordset is this:
ws.Credentials = System.Net.CredentialCache.DefaultCre
AuthorData.Merge(ws.GetAuthors())
(AuthorData is a dataset on the client.) But don't XML Web Services
return XML? Yes, they do. And yet all you see here is references to
datasets. They're the same thing.
Con = SQLStringConnect (;
"Driver={SQL Server};server=(local);UID=sa;PWD=;databa
This puts the results of the query in a cursor named SQLResult. (In fact,
that's the default cursor name if you don't supply that third parameter.)
In the past, it was much more complicated to return data from a remote
server. Not any longer. You can configure SQL Server in such a way that
query output can be sent directly to your program using HTTP, without
any third-party software at all, using just a few lines of code.
In the example in Listing 7.17, the plus signs in the URL are called
encoding. URLs can't contain blanks, so either you replace blanks with
something like a plus and let IIS remove them, or you call an encoding
routine. You can use CHRTRAN(url, " ", "+" ) to replace blanks
with plus signs.
WITH oXMLDOM
.Load ( "http://localhost/xmldir?sql=SELECT ;
+ *+FROM+authors+FOR+XML+AUTO&root=root")
ENDWITH
Before you try this out on your own computer, you'll need to select Start,
Programs, SQL Server, Configure SQL XML Support in IIS and specify
the name of a directory to be used to hold the results of this type of
query.
Figure 7.5 shows the first page of the dialog that you use to configure
SQL Server via HTTP. The Virtual Directory name is the name that
appears in the URL string; the Actual Directory name is the fully qualified
name of the directory on your hard drive. It's easiest if you give them both
the same name. If the new physical directory doesn't yet exist, the wizard
will create it for you when you close the dialog.
Similarly, you can call a Web service written in either FoxPro or in one of
the .NET languages. As we'll see below, you can create a Web service in
a minute or two. Web services can be of arbitrary complexity, so don't
think that there are things you can do in SQL but not with Web servicesall
it takes is time. And you can use either DBFs or SQL Server (except
when using HTTP SQL as described in the preceding section, which
obviously only works with SQL).
But there are other MSXML2 components that are also useful. Above we
used the MXSML2.DOMDocument class to send a query to SQL Server.
But we can also use the MSXML2.XMLHTTP class to return XML from a
URL, as shown in Listing 7.18.
loXMLHTTP.Open("GET", "http://localhost/AppName/SendMe
loXMLHTTP.Send()
XMLTOCURSOR (loXMLHTTP.ResponseBody)
This will return anything, including a Web page. But because you'd need
a browser control to display a Web page, it's not very useful unless you
need to do some screen-scraping. Actually, screen-scraping was how we
got output from Web sites before XML Web Services were the standard;
now you only have to screen-scrape if they don't want you to have their
page content.
USE CLIENTS
Now we can use the code shown in Listing 7.19 to read XML from a
remote source and validate it using the structure stored in
CLIENTS.XSD.
loXML.Schemas = loXMLSchema
loXML.Asynch = .F.
loXML.Load (http://www.lespinter.com/SvcsWebXML/Client
IF loXML.ParseError.ErrorCode <> 0
MessageBox ( ;
+ [Cause: ] + loXML.ParseError.Reason )
ENDIF
Similarly, the Visual Studio XML Editor can be used to create an XML
schema (see Figure 7.9). Create a Windows Forms project, add a
SQLDataAdapter and configure its connection and SELECT statement.
Then right-click on the DataAdapter and select Generate Dataset, and
an XSD file will be added to your project.
For the rest of the programming world, there's no miracle. It's just
codeoften lots of code.
SET MULTILOCKS ON
SELECT TEST
CursorSetProp([Buffering], 5 )
X = XMLUpdateGram()
STRTOFILE( x, "UpdateGram.xml" )
<root xmlns:updg="urn:schemas-microsoft-com:xml-update
<updg:sync>
<updg:before/>
<updg:after>
<test>
<contactid>1</contactid>
<fullname>Les Pinter</fullname>
<phone>324-4321</phone>
</test>
</updg:after>
</updg:sync>
</root>
SET MULTILOCKS ON
SELECT TEST
CursorSetProp([Buffering], 5 )
CursorSetProp([KeyFieldList],[ContactID])
X = XMLUpdateGram()
STRTOFILE( x, "UpdateGram.xml" )
<root xmlns:updg="urn:schemas-microsoft-com:xml-update
<updg:sync>
<updg:before>
<test>
<contactid>1</contactid>
<fullname>Les Pinter</fullname>
</test>
</updg:before>
<updg:after>
<test>
<contactid>1</contactid>
<fullname>Juan Carral</fullname>
</test>
</updg:after>
</updg:sync>
</root>
SET MULTILOCKS ON
SET DELETED ON
SELECT TEST
CursorSetProp([Buffering], 5 )
DELETE NEXT 1
X = XMLUpdateGram()
STRTOFILE( x, "UpdateGram.xml" )
<root xmlns:updg="urn:schemas-microsoft-com:xml-update
<updg:sync>
<updg:before>
<test>
<contactid>1</contactid>
<fullname>Les Pinter</fullname>
<phone>324-4321</phone>
</test>
</updg:before>
<updg:after/>
</updg:sync>
</root>
The bad news is that although SQL Server can read diffgrams, FoxPro 7
can't. And anyway, neither can read updategrams. Visual FoxPro 8 has
diffgrams, which are different from updategrams, and Visual FoxPro 8
can read and apply them to add, update, and delete records.
So, unless you write your own UpdateGram parser for receiving and
processing these animals, they're interesting but not relevant with
working with DBFs.
DO SearchForm to lcSelectedRecordKey
loXMLHTTP.AddPostVar ( lcCmd )
loXMLHTTP.Send()
XMLTOCURSOR (loXMLHTTP.ResponseBody)
CursorSetProp([Buffering],5)
The user modifies the record in the current screen, then clicks on the
Save button. This is where we create the DiffGram and send it to the
server, as shown in Listing 7.27.
oXML.AddTableSchema ( "Clientes" )
oXML.IsDiffGram = .T.
oXML.UTF8Encoded = .T.
loXMLHTTP.AddPostVar ( lcXML )
loXMLHTTP.Open([POST], [http://localhost/Fox/UpdateCli
loXMLHTTP.Send()
When the Server program UpdateClientsTable runs, it extracts the
diffgram from the post buffer and uses it to update the table, as shown in
Listing 7.28.
oXML.LoadXML ( lcXML )
oCA.DataSource = "Native"
oCA.CursorAttach ( "Clients" )
oTable = oXML.Tables.Item(1)
oField = oTable.Fields.Item(1)
oField.KeyField = .T.
oTable.ApplyDiffGram ( "Clients", oCA, .T. )
CLOSE TABLES
CLOSE DATABASE
In FoxPro 8, XML has finally received the support that we lacked for
communicating with the rest of the world. The new base class
CursorAdapter replaces what we used to do with remote views, and to
a degree what we did with SQL Pass-Through. And it does so in a
homogeneous way, so that only one syntax has to be learned regardless
of the data store. It automates the tasks of inserting, updating, and
deleting records on remote tables when we make changes locally. What's
new is that it also works with XML.
Let's create a new form called XMLForm1. Open the Data Environment
and right-click. You'll see several options, among them Add
CursorAdapter and Builder. The first lets you construct the
CursorAdapter manually, but the Builder is helpful for learning how it
works. For now, we'll use the Builder.
<Root>
:schemas-microsoft-com:xml-msdata">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="nombre">
<xsd:simpleType>
<xsd:restriction base="xsd:string"
<xsd:maxLength value="20"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="apellidos">
<xsd:simpleType>
<xsd:restriction base="xsd:string"
<xsd:maxLength value="30"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="telefono">
<xsd:simpleType>
<xsd:restriction base="xsd:string"
<xsd:maxLength value="15"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:anyAttribute namespace="http://www.w3.org
processContents="lax"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<clientes>
<clave>1</clave>
<nombre>Juan</nombre>
<apellidos>del Pueblo</apellidos>
<telefono>3312-1234</telefono>
</clientes>
<clientes>
<clave>2</clave>
<nombre>Carlos</nombre>
<apellidos>Estrada</apellidos>
<telefono>4343-3323</telefono>
</clientes>
<clientes>
<clave>3</clave>
<nombre>Jimena</nombre>
<apellidos>Sánchez</apellidos>
<telefono>2014-1914</telefono>
</clientes>
</Root>
Open the Data Environment and right-click somewhere inside it. Select
Builder from the context menu. You'll see the screen shown in Figure
7.10.
Since Visual FoxPro 3, the remote view has been the mechanism that
permitted us to build a cursor containing the contents of a table from a
remote source such as SQL Server, an Access MDB, or any other data
source. The CursorAdapter lets us use a single syntax for any data
source. This lets us migrate the application from DBF to some other data
source at any time without changing any code; we simply change the
data source from Native (that is, FoxPro DBF) to something else. We
also get to practice the use of disconnected recordsets, which are the
gospel according to Microsoft.
The first page of the DataEnvironment Builder (see Figure 7.11) lets us
select the data source: ADO, Native (DBF), ODBC, and XML. Choose
XML for this example. Click on the second tab of the DataEnvironment
Builder, "2. Cursors", to continue.
Figure 7.11. Select the data source for the
DataEnvironment Builder.
After you've selected XML, the Cursors page will launch the
CursorAdapter Builder to create a CursorAdapter, which will open the
XML file, read the XSD, and create the cursor's structure. Click on New to
launch the CursorAdapter Builder shown in Figure 7.12.
Figure 7.14. Creating the schema for the cursor from the
XML.
Click OK to close the CursorAdapter Builder and click OK again to close
the Data Environment Builder.
Two steps remain to be done in code. First, open the Data Environment,
and right-click on Cursor1, select Code. Select the AutoOpen procedure,
and you'll see that the CursorAdapter Wizard has added several lines of
generated code. At the end of this generated code, add these two lines:
THIS.CursorFill()
Next, add a grid on the form, open the Properties Sheet, and enter the
name of the cursor (Cursor1) as the RecordSource. Press Ctrl+E to
save the form and run it. The result appears in Figure 7.15.
DS.ReadXml("C:\XML1.XML")
DataGrid1.DataSource = DS.Tables(0)
That's probably the first time we've seen something that's easier in Visual
Basic .NET than it is in FoxPro. But there are a few more as well, as we'll
see in later chapters.
TIP
The XMLAdapter class is also new in Visual FoxPro 8. It's similar to the
CursorAdapter class, except that it has more methods specifically
designed for dealing with XML. It contains most, if not all, of the functions
we need in Visual FoxPro to manipulate XML, and this makes the job
much easier than it was in Visual FoxPro 7.
In order to use it, you first have to load an XML Document. You can do so
using its LoadXML() method to load XML from either a string variable or
from a file. Or, you can use Attach() to load an XML document that's
already been loaded into an XMLDOMDocument object, or from HTTP.
You can also use AddTableSchema to read a FoxPro cursor.
Method Description
Data Islands
Internet Explorer (and probably other browsers as well) support the use
of Data Islands, which permit us to output a file containing the
instructions for formatting data, and let the user's browser do the work.
This is especially attractive because servers have the ability to cache
pages as they're served. The next person who requests the same page
gets the in-memory copy of the page, which is about a thousand times
faster than doing another query and formatting the output.
The HTML page shown in Listing 7.30 contains an example of the use of
Data Islands.
<body>
<XML ID="FantasyIsland">
<clients>
<client>
<first>Juan</first>
<last>Perez</last>
</client>
<client>
<first>Les</first>
<last>Pinter</last>
</client>
</clients>
</XML>
</tr>
</table>
<hr>End
</body>
</html>
The example shown in Listing 7.31 reads an XML file, locates a section
of text, and produces a list of the related elements using an embedded
XPATH query. aaa and bbb are elements in the namespace of the XML
that is to be read:
Listing 7.31. Visual Basic .NET Code to Read XML and List
Part of Its Contents Using an Embedded XPATH Query
loDOM.Load ( "CLIENTES.XML")
? loNode.XML
ENDFOR
The two lines of code set off in the middle of Listing 7.31 define the
selection criteria for the nodes to be included. It's a very compact way to
do something that otherwise would have to be required code. He who
codes least codes best.
<?xml version="1.0"?> <xml:stylesheet version= "1.0"
xmlns:xsl=http://www.wc.org/1999/XSL/Transform>
<xsl:template match="/"> <html>
</xsl:template>
</xsl:stylesheet>
Creating Menus
Subclassing Controls
Data Binding
Tools
I'm warming up to the Visual Basic .NET Screen Designer. It certainly has
everything you could want, and in fact has more controls, more IDE
features, and more ways of doing things than does Visual FoxPro. But
somehow, Visual FoxPro is cozy. And you can do almost anything with
Visual FoxPro that you can with Visual Basic .NET.
With those caveats, Table 8.1 gives the approximate equivalencies of the
base control classes. We'll cover important differences as we go through
the chapter.
Label Label
TextBox TextBox
Commandbutton Button
OptionGroup RadioButton
CheckBox CheckBox
ComboBox Combobox
ListBox ListBox
Spinner NumericUpDown
Grid DataGrid
Timer Timer
Grid DataGrid
OLEControl Various
OLEBoundControl Various
Shape Panel
Container Panel
Separator N/A
HyperLink LinkLabel
GetColor() ColorDialog
CheckBox CheckedBoxList
MSComctl2.ocx DateTimePicker
GetDir() FolderBrowserDialog
GetFont() FontDialog
MSComCtlLib ImageList
MSComCtlLib Listview
MSComctl.ocx MonthCalendar
GetFile() OpenFileDialog
MSComCtlLib ProgressBar
RichTextLib RichTextBox
PutFile() SaveFileDialog
N/A Splitter
MSComCtlLib StatusBar
PageFrame TabControl
Toolbar Class,
Toolbar
MSComctl.ocx
MSComCtlLib TrackBar
MSComCtlLib TreeView
I'm tempted to say that this list consists of apples and oranges. FoxPro's
base control list is actually only 21 controls: Label, TextBox, EditBox,
CommandButton, CommandGroup, OptionGroup, CheckBox,
ComboBox, ListBox, Spinner, Grid, Image, Timer, PageFrame,
ActiveXControl, ActiveXBoundControl, Line, Shape,
Container, Separator, and HyperLink. But on my laptop alone I
have over 350 ActiveX controls, and every week some vendor sends me
another dozen or two. When registered with RegSvr32.exe, these
controls appear on the Tools, Options, Controls page, and on the Visual
Studio toolbox under each vendor's heading bar. So it's hard to say that
one language has something that the other doesn't. Except for the
NotifyIcon, HelpProvider, and Splitter controls, each has
everything that the other has, although the implementation is sometimes
totally different.
In FoxPro, there are two ways to build a Windows Forms project. You can
either use _Screen (the FoxPro IDE screen) as the container for the rest
of your forms, or you can designate your own top-level form by setting
the ShowWindow property as follows:
0 - In Screen
1 - In Top-Level form
2 - As Top-Level form
If you use a top-level form, you have to designate the forms that run
within it as ShowWindow type 1, In Top-Level form. This is called a
Multiple Document Interface (MDI). The default is Single Document
Interface (SDI), and it's what you've been using for years. You can use
either.
If your startup form is a top-level form, you have to select the Top-Level
Form Menu option in the Menu Designer. You then have to load the menu
inside the form by executing the following command in the form's Load
event:
WindowState = FormWindowState.Maximized
StartPosition = FormStartPosition.CenterScreen
In Visual Basic .NET, attempting to set a value for either of these form
properties automatically displays the appropriate Enum the instant you
type the "=" (see Figure 8.2). So, if you're writing code that someone else
might use and there are several options for a property, you might want to
create the appropriate Enum in order to take advantage of the way that
IntelliSense works.
The design is saved in a table with the extension .mnx (.mnt for the
memo fields). When you're done, you must generate the source code for
the menu by selecting the Generate option from the IDE menu. The
resulting code is stored in a text file with the extension .mpr. Therefore,
in your MAIN program code, you include the command
DO MENU.MPR
All in all, the FoxPro menu is quite functional, but it's a little hard to get
used to, and I wouldn't call it intuitive.
The Visual Basic .NET menu is a thing of beauty. It's intuitive, easy to
understand, and easy to use. Unlike FoxPro's Menu Designer approach,
the MainMenu control is selected from the Windows Form controls
toolbar and dropped on the MainForm page. When selected, it appears
in the upper-left corner of the screen. You can type directly into the pads
and bars. Moving to the right or left of the last available selection
produces a new empty box into which you can type the appropriate
prompt. Selections can be moved around.
Changes to the menu control produce code in the form itself that is
directly viewablethere is no delayed code generation. To make the code
more readable, you'll want to right-click on the menu control, turn on the
Edit Names option, and type names for each menu selection. For
example, I call the File pad mnuFile, the File, Exit selection below it
mnuFileExit, and so forth. It makes it a lot easier to read the code, and
it's easy.
To add code for any menu pad or bar, double-click on its name or caption
and the corresponding code is displayed in the code window. Visual
Basic .NET uses Handles clauses to connect the code you write to a
particular menu component. The code is actually quite intuitive.
oFrm.Show()
Similarly you can instantiate and call methods on any other class. The
Visual Basic .NET MainMenu control is absolutely one of the best things
about the Visual Basic .NET Screen Designer. During a design review, I
once irritated the FoxPro design team by alluding to the provenance of
this lovely control, which came straight from Delphi (along with the entire
Borland programming team).
Ctrl.Enabled = .F.
ENDFOR
Ctrl.Enabled = False
Next
CREATE CLASS MyLabel OF FormControls AS Label
CREATE CLASS MyText OF FormControls AS TextBox
CREATE CLASS MyEdit OF FormControls AS EditBox
CREATE CLASS MyButton OF FormControls AS
CommandButton CREATE CLASS MyGroup OF
FormControls AS CommandGroup CREATE CLASS
MyRadio OF FormControls AS OptionGroup CREATE
CLASS MyCheck OF FormControls AS CheckBox
CREATE CLASS MyCombo OF FormControls AS
ComboBox CREATE CLASS MyList OF FormControls
AS ListBox CREATE CLASS MySpin OF FormControls
AS Spinner CREATE CLASS MyGrid OF FormControls
AS Grid CREATE CLASS MyImage OF FormControls
AS Image CREATE CLASS MyTimer OF FormControls
AS Timer CREATE CLASS MyFrame OF FormControls
AS PageFrame CREATE CLASS MyLine OF
FormControls AS Line CREATE CLASS MyShape OF
FormControls AS Shape CREATE CLASS MyPanel OF
FormControls AS Container CREATE CLASS MyLink
OF FormControls AS HyperLink
...
EditableFields =
[MyText|MyEdit|MyCheck|MyCombo|MyRadio|MySpin]
PARAMETERS OnOff
IF UPPER(Ctrl.Class) $
UPPER(THISFORM.EditableFields) Ctrl.Enabled =
OnOff
ENDIF
ENDFOR
Imports System.Windows.Forms
Imports System.Drawing
Text = ""
ByVal e As EventArgs) _
ByVal o As Object, _
ByVal e As EventArgs) _
End Class
Text = ""
ByVal o As Object, _
ByVal e As EventArgs) _
ByVal o As Object, _
ByVal e As EventArgs) _
Handles MyBase.Leave BackColor = BackColor.White
ForeColor = ForeColor.Black End Sub
End Class
End Class
Text = "Lbl"
End Sub
End Class
End Class
ByVal e As System.EventArgs) _
End Sub
ByVal e As System.EventArgs) _
End If
End If
End Sub
CtrlType =
Ctrl.GetType.ToString.ToUpper.Substring(LastPeriod)
FName = Ctrl.Name.Substring(3) If
UserControls.IndexOf(CtrlType) > 0 _
End Sub
End Sub
Me.BindingContext(dsEmps).Position = 0
Me.BindingContext(dsEmps).Position =
Me.BindingContext(dsEmps).Position + 1
h = SQLSTRINGCONNECT( _
"Driver={SQL Server};Server=
(local);Database=Northwind;UID=sa;PWD=;")
SQLEXEC( h, "SELECT * FROM EMPLOYEE") COPY
TO Employee
USE Employees
USE Orders
USE OrderDetails
ShowTips = .T.
AutoCenter = .T.
Caption = "Employees"
MaxButton = .F.
MinButton = .F.
Width = 270
Height = 360
<span
class="docEmphStrong">THISFORM::Load</span>
CREATE CURSOR RelatedOrders ( OrderID Integer,
OrderTotal Numeric(10,2) ) <span
class="docEmphStrong">Init</span> event:
THISFORM.CalculateOrderTotals
<span class="docEmphStrong">*
CalculateOrderTotals</span> method: SELECT
Employees
RecNo = RECNO()
EmpID = EmployeeID
SELECT ;
OrderDetails.OrderID, ; SUM(Quantity*UnitPrice) AS
OrderTotal ; FROM Orders, OrderDetails ; WHERE
Orders.EmployeeID = EmpID ; AND Orders.OrderID =
OrderDetails.OrderID ; GROUP BY
OrderDetails.OrderID ;
ORDER BY OrderDetails.OrderID ;
INTO CURSOR C1
SELECT RelatedOrders
ZAP
APPEND FROM DBF("C1")
USE IN C1
GO TOP
SELECT Employees
GO ( RecNo )
THISFORM.Refresh
<span class="docEmphStrong">cmdFirst::Click:</span>
SELECT Employees
GO TOP
THISFORM.CalculateOrderTotals
<span class="docEmphStrong">cmdPrev</span>::Click
SELECT Employees
SKIP -1
IF BOF()
GO TOP
ENDIF
THISFORM.CalculateOrderTotals
<span class="docEmphStrong">cmdNext</span>::Click
SELECT Employees
SKIP
IF EOF()
GO BOTTOM
ENDIF
THISFORM.CalculateOrderTotals
<span class="docEmphStrong">cmdLast</span>::Click
SELECT Employees
GO BOTTOM
THISFORM.CalculateOrderTotals
Handles MyBase.Load
Me.SqlDataAdapter1.Fill(DsEmployees1, "Employees")
LoadGrid()
End Sub
Da = New SqlDataAdapter(CmdStr,
Me.SqlConnection1.ConnectionString)
dsEmployeeOrders.Clear()
Try
End Try
End Sub
Private Sub cmdTop_Click( _
Handles cmdBottom.Click
Me.BindingContext(DsEmployees1,
"Employees").Position = 0
LoadGrid()
End Sub
Me.BindingContext(DsEmployees1,
"Employees").Position -= 1
LoadGrid()
End Sub
Me.BindingContext(DsEmployees1,
"Employees").Position += 1
LoadGrid()
End Sub
Public Sub LastRecord()
Me.BindingContext( _
DsEmployees1, "Employees").Position = _
DsEmployees1.Tables(0).Rows.Count - 1
LoadGrid()
End Sub
Me.BindingContext("Employees").EndCurrentEdit()
SqlDataAdapter1.Update(DsEmployees1)
MessageBox.Show("Database updated.", _
Me.Text, MessageBoxButtons.OK, _
Me.Text, MessageBoxButtons.OK, _
End Sub
GetData()
With grdOrders
.CaptionText = "Orders"
.DataSource = dsEmployeeOrders
.DataMember = "Orders"
End With
grdOrders.TableStyles.Clear()
.MappingName = "EmployeeOrders"
End With
.Width = 75
End With
.MappingName = "SubTotal"
.Format = "c"
.Width = 75
End With
grdTableStyle1.GridColumnStyles.AddRange _
End Sub
These work like Valid clauses in FoxPro, except that this
particular Valid clause is not needed with date fields in
FoxPro. It's kind of a pain in the neck to have to do
something that was taken care of automatically back in
1982 in the first version of dBase II, but it's just this same
code over and over; so copy it to a code snippet and keep
it handy.
PARAMETERS pdDate
IF EMPTY ( pdDate )
pdDate = DATE()
ENDIF
WITH THISFORM
.Date = pdDate
.Calendar.Month = MONTH(pdDate)
RETURN THISFORM.Date
MyDate::DblClick:
THIS.Value = Result
ENDIF
MyLinkLabel::Click
IF WEXIST("Customer")
Customer.Show
ELSE
DO FORM Customer NAME Customer
Customer.Show
ENDIF
DO MENU.MPR WITH THIS, .T.
TextBox::GotFocus()
PROCEDURE Resize
GetProps ( THISFORM )
THISFORM.Resize
THISFORM.UserFieldsTitle.Visible = (
THISFORM.UserFields > 0 ) FOR I = 1 TO
THISFORM.UserFields ObjName = [Text] +
TRANSFORM(I,[@L ##]) ClsName = [UserTextBox]
ClsLib = [pinter.vcx]
PROCEDURE cmdsetbgcolor.Click
SaveColor = THISFORM.BackColor
THISFORM.BackColor = GETCOLOR()
THISFORM.BackColor = SaveColor
ELSE
SetProp( THISFORM, [BackColor] )
ENDIF
ENDPROC
PROCEDURE cmdsetposition.Click
* Program-ID...: SetProps.PRG
SELECT 0
USE FormSettings
ENDIF
SELECT FormSettings
* 1 FORMNAME Character 10
* 2 PROPERTY Character 20
* 3 PROPTYPE Character 1
* 4 PROPVALUE Character 20
GO TOP
LOCATE FOR UPPER(FormName) =
UPPER(oForm.Name) AND Property = cProp IF NOT
FOUND()
APPEND BLANK
REPLACE FormName WITH oForm.Name, Property
WITH cProp
ENDIF
REPLACE NEXT 1 PropValue WITH []
&Cmd
* Program-ID...: GetProps.PRG
PARAMETERS oForm
SaveAlias = ALIAS()
SELECT 0
USE FormSettings
ENDIF
SELECT FormSettings
IF _Tally = 0
RETURN
ENDIF
cPropVal = IIF(cPropVal=[.T.],.T.,.F.)
ENDCASE
oForm.&Cprop. = cPropVal
ENDFOR
IF NOT EMPTY ( SaveAlias )
SELECT ( SaveAlias )
ENDIF
ENDPROC
PROCEDURE ListRecords
WITH THISFORM
* Remove any previously displayed objects: CellCount =
0
DIMENSION CellList(1)
CellCount = CellCount + 1
IF CellCount > 0
Flipper = 1
GO ( .StartTop )
.LastTop = RECNO()
CellTop = .CellTop
.&CellName2..Visible = .T.
.&CellName2..InputMask = [#,###.##]
CellTop = CellTop + 23
PROCEDURE command1.Click
THISFORM.Image1.Visible = .T.
THISFORM.PageTops(1) = 1
THISFORM.cmdNext.Click
ENDPROC
PROCEDURE cmdprevious.Click
WITH THISFORM
.PageCounter = .PageCounter - 1
IF .PageCounter < 2
.PageCounter = 1
.cmdPrevious.Enabled = .F.
ENDIF
.StartTop = .Pagetops(.PageCounter) .ListRecords
.cmdNext.Enabled = .T.
ENDWITH
ENDPROC
PROCEDURE cmdnext.Click
WITH THISFORM
.StartTop = RECNO()
.ListRecords
.PageCounter = .PageCounter + 1
THIS.Enabled = .F.
ENDIF
IF .PageCounter > 1
.cmdPrevious.Enabled = .T.
ENDIF
ENDWITH
ENDPROC
* Form KeyPress event code:
.Grid1.DblClick
NODEFAULT
RETURN
ENDIF
IF NOT BETWEEN ( nKeyCode, 64, 128 )
RETURN
ENDIF
.Label3.Visible = .T.
.SearchLabel.Visible = .T.
THIS.SearchLabel.Caption = UPPER(CHR(nKeyCode))
LOCATE FOR
UPPER(EVALUATE(THISFORM.KeyField)) =
THIS.SearchLabel.Caption .Label3.Visible = .T.
IF NOT FOUND()
.Grid1.SetFocus
Start = SECONDS()
.KeyPreview= .F.
a = INKEY() THIS.SearchLabel.Caption =
THIS.SearchLabel.Caption + UPPER(CHR(a)) LOCATE
FOR UPPER(EVALUATE(THISFORM.KeyField)) =
THIS.SearchLabel.Caption .Label3.Visible = .T.
.Grid1.SetFocus
ENDDO
.KeyPreview = .T.
.Label3.Visible = .F.
.SearchLabel.Visible = .F.
ENDWITH
IIF(DateTime-DateTime()>60,RGB(0,0,255), ;
IIF(DateTime-DateTime()>30,RGB(255,255,0), ;
IIF(DateTime-DateTime()>15,RGB(255,128,0), ;
RGB(255,0,0))))
WITH THISFORM.List1
.Clear
SCAN
.AddListItem ( Column1 ) Row = .NewItemID
initialx = 0
x=0
initialy = 0
y=0
normalcolor = (RGB(43,85,128))
livecolor = (RGB(192,0,0))
textcolor = (RGB(255,255,255))
cellheight = 22
cellwidth = 100
Name = "javamenu"
DIMENSION menu[1,1]
DIMENSION action[1,1]
PROCEDURE showmenu
PARAMETERS nItemNumber
WITH THISFORM
IF NOT EMPTY ( nItemNumber )
.LockScreen = .T.
oList ( I ) = .Objects(I).Name
ENDFOR
FOR I = 1 TO ALEN ( oList )
IF oList(I) = [Bar]
.RemoveObject ( oList(I) )
ENDIF
ENDFOR
.LockScreen = .F.
ENDIF
ENDWITH
THIS.LoadMenu ( nItemNumber )
PROCEDURE addcell
.Top = THIS.X
.Left = THIS.Y
.BackColor = THIS.NormalColor
.ForeColor = THIS.TextColor
.ActiveColor = THIS.LiveColor
.Width = THIS.CellWidth
.Height = THIS.CellHeight
.Visible = .T.
ENDWITH
ENDWITH
ENDPROC
PROCEDURE loadmenu
PARAMETERS nItemNum
SELECT 0
USE MENU
ENDIF
DIMENSION THIS.Menu ( 1, 2 )
IF EMPTY ( nItemNum )
PROCEDURE bouncingform
oCell.Bounce
DO FORM ( FormName )
ENDPROC
PROCEDURE Init
THIS.X = THIS.Top
THIS.Y = THIS.Left
THIS.X = THIS.InitialX
ENDIF
IF THIS.InitialY > 0
THIS.Y = THIS.InitialY
ENDIF
THIS.ShowMenu()
ENDPROC
ENDDEFINE
ScrollBars = 0
SpecialEffect = 1
Width = 136
BorderColor = RGB(255,255,255)
activecolor = (RGB(255,0,0))
droppable = .F.
Name = "menuitem"
inactivecolor = .F.
PROCEDURE Click
IF THIS.Droppable
WITH THISFORM
DIMENSION oList ( .Objects.Count ) FOR I = 1 TO
.Objects.Count
oList ( I ) = .Objects(I).Name
ENDFOR
FOR I = 1 TO ALEN ( oList )
a=INKEY(.02) THIS.Top = I
ENDFOR
A=INKEY(.1) THIS.Visible = .F.
ENDIF
ActionNum = VAL ( RIGHT ( THIS.Name, 1 ) ) Cmd =
THISFORM.JavaMenu1.Menu ( ActionNum, 2 ) &Cmd
IF THIS.Droppable
THISFORM.RemoveObject ( THIS.Name )
ENDIF
ENDPROC
PROCEDURE Init
IF PCOUNT() > 0
PROCEDURE MouseEnter
WITH .JavaMenu1
.X = THIS.Top
.Y = THIS.Left + THIS.Width
PROCEDURE MouseLeave
WITH THIS.Parent
DateExpr = []
DO CASE
* Both checked, no date entered - assume all dates CASE
NOT ISNULL ( .FromDate.Object.Value ) ; AND NOT
ISNULL ( .ToDate.Object.Value ) ; AND EMPTY (
.FromDate.Object.Value ) ; AND EMPTY (
.ToDate.Object.Value ) DateExpr = [BETWEEN (] +
.DateFieldName ; + [, {1/1/1900}, {12/31/2089})]
&Dest = DateExpr
THISFORM.Refresh
.Text2.ControlSource = [THISFORM.DateFilter]
.NewObject ( ; [DateRange1],[DateRange],[pinter.vcx],
[],[ReportDate],[THISFORM.DateFilter])
ENDIF
WITH .DateRange1
oItem.Visible = .T.
ENDFOR
.Top = THIS.Top
.Visible = .T.
.SetFocus
ENDWITH
ENDWITH
WITH THISFORM
.Edit1.Value = [SELECT * FROM ORDERS]
Cmd1 = []
Cmd2 = []
In the next chapter, we'll look at reporting, the heart of many database
applications. If you thought that reporting was easy in FoxPro and hard in
Crystal Reports, which is .NET's reporting tool, you might be basing your
opinion on outdated information. Let's see if an update improves your
attitude toward reporting with Crystal Reports.
Chapter 9. Searching and Filtering in
Visual FoxPro and Visual Basic .NET
IN THIS CHAPTER
What can you understand about a few hundred thousand records? Not
much. The difference between data and information is understanding,
and searching and filtering the data can provide that understanding.
Record Filtering in FoxPro
In Visual FoxPro, the SET FILTER command provides one mechanism
for viewing only certain records. To use it, define the parameters for
selecting filter values and issue the command.
The code for the form is located in four places, as shown in Listing 9.1.
THISFORM.Combo1.SetFocus
THISFORM.Release
SELECT Orders
THISFORM.Grid1.Refresh
GO TOP
Some aspects of data handling are so much easier in FoxPro than they
are in any other language that we've become a little spoiled. For
example, the form's Data Environment opens the tables, then closes
them when the form is closed. The SET FIELDS command in FoxPro
limits the fields displayed to the four named fields.
This form shows how powerful FoxPro's SET commands are. As we'll see
shortly, there are no SET commands in Visual Basic .NET, so all data
management has to be done individually on each dataset or data view.
Record Filtering in Visual Basic .NET
Figure 9.2 shows the equivalent form in Visual Basic .NET.
I took the liberty of selecting one of the available styles for the grid. It's a
nice compensation for only being able to adjust the individual column
widths in code. (I expect that to be fixed in the next release of Visual
Studio.)
Preparing the data for this example was a little more involved than it was
in Visual FoxPro. First, I created a new Windows Forms application
called SimpleFiltering, and added a SQLDataAdapter to the blank form.
The resulting wizard asked for a connection, at which point I added a
connection to the database that we've been using for our examples.
For the DataAdapter (which you could call daCountries, although I
didn't change the default name of DataAdapter1), I specified the SQL
statement SELECT Country FROM Countries. I also checked the
Advanced Options button and unchecked the Generate Insert, Update
and Delete Statements option because all we're doing is viewing the
records.
Figure 9.3. Adding the dataset for the second data adapter.
The Load event fills the datasets. Without this, you won't see a thing:
SqlDataAdapter1.Fill(DsCountries1, "Countries")
SqlDataAdapter2.Fill(DsOrders1, "Orders")
dv = DsOrders1.Tables(0).DefaultView
DataGrid1.DataSource = dv
CREATE CLASS SearchForm OF Pinter AS FORM
Height = 250
Width = 375
AutoCenter = .T.
ControlBox = .F.
cmdSelect::Click
WITH THISFORM
.ReturnValue =
TRANSFORM(EVALUATE(.KeyField)) .Hide
.Release
ENDWITH
cmdCancel::Click
WITH THISFORM
.ReturnValue = []
.Hide
.Release
ENDWITH
Height = 200
Left = 10
Width = 355
Name = Grid1
ScrollBars = 2-Vertical
DeleteMark = .F.
WITH THISFORM
.LockScreen = .T.
WITH .Grid1
.Width = THIS.Width - 20
.Height = THIS.Height - 50
ENDWITH
.cmdSelect.Left = .Width/2 - .cmdSelect.Width/2
.cmdSelect.Top = .Height - 32
.cmdCancel.Top = .Height - 32
.Shape1.Top = .Height - 37
.LockScreen = .F.
ENDWITH
DEFINE CLASS MyCol as Column
FontSize = 11
Width = 150
Visible = .T.
Sparse = .F.
HeaderClass = "MyHeader"
HeaderClassLibrary = "GridLib.prg"
ENDDEFINE
DEFINE CLASS MyHeader as Header
Caption = "MyHeader"
FontSize = 11
PROCEDURE Click
* PROCEDURE Init
ErrorStatus = []
WITH THISFORM
TRY
DIMENSION laTags(1)
laTags = ""
ENDIF
WITH .Grid1
.RecordSource = THISFORM.TableName
.ColumnCount = 0
.Visible = .T.
FieldCount = GETWORDCOUNT(
THISFORM.FieldList, "," ) TotalWidth = 0
.AutoFit()
* Add the column widths to resize the grid and the form:
FOR EACH Col IN .Columns TotalWidth = TotalWidth +
Col.Width
ENDFOR
.Width = TotalWidth + 25
ENDWITH
THISFORM.Width = .Grid1.Width + 20
.Refresh()
RETURN .F.
ENDIF
* Finally, since the form size probably changed, recenter
it now: THISFORM.AutoCenter = .T.
Imports System.Data.SqlClient
ByVal e As System.EventArgs) _
Handles MyBase.Load
cn = New SqlConnection( _
"Server=localhost;Database=Northwind;" _
+ "UID=sa;PWD=;")
da = New SqlDataAdapter( _
+ Strings.FormatDateTime(Today.Now,
DateFormat.ShortDate) End Sub
ByVal e As System.EventArgs) _
Handles btnFind.Click
ByVal e As System.EventArgs) _
Handles ComboBox1.SelectedIndexChanged
ds.Tables(0).DefaultView.Sort = _
ByVal e As System.EventArgs) _
Handles Button1.Click
cn.Open()
+ MyText1.Text.ToUpper.Trim + "%'"
da.Fill(ds, TableName)
dv = ds.Tables(0).DefaultView
DataGrid1.DataSource = dv
HowMany = cmd.ExecuteScalar
If MsgBox(s, _
MsgBoxStyle.Exclamation, Application.ProductName)
_
= MsgBoxResult.No Then Return
End If
End If
.Grid1.RecordSource = "SQLResult"
ENDWITH
DataAdapter1:
SELECT * FROM Orders
DataAdapter2:
ByVal e As System.EventArgs) _
Handles MyBase.Load
SqlDataAdapter1.Fill(DsOrders1, "Orders")
DataGrid1.DataMember = "Orders"
With SqlDataAdapter2
.SelectCommand.Parameters(0).Value = _
DsOrders1.Tables("Orders").Rows(DataGrid1.CurrentR
.Fill(DsOrders1, "[Order Details]") End With
DataGrid1_CurrentCellChanged(Me, New
System.EventArgs) End Sub
Private Sub DataGrid1_CurrentCellChanged( _
ByVal e As System.EventArgs) _
Handles DataGrid1.CurrentCellChanged
DsOrders1.Tables("[Order Details]").Clear() With
SqlDataAdapter2
.SelectCommand.Parameters(0).Value = _
DsOrders1.Tables("Orders").Rows(DataGrid1.CurrentR
.Fill(DsOrders1, "[Order Details]") End With
With DataGrid2
.DataSource = DsOrders1
.ColumnCount = NumWords
.RecordSource = THISFORM.ViewName
.RecordSourceType = 1
GridWidth = 0
FOR I = 1 TO NumWords
.Columns(I).Header1.Caption = THISFORM.Heading
(I) GridWidth = GridWidth + VAL(
THISFORM.ColWidth(I) ) FldName =
THISFORM.ViewName + [.] + THISFORM.Field (I)
.Columns(I).ControlSource = FldName
ENDFOR
Multiplier = ( THIS.Width / GridWidth ) * .90
FOR I = 1 TO NumWords
Ctrl.Enabled = .T.
IF EMPTY ( .TableName )
.ColWidths = [1,1,1,1,1]
ENDIF
IF EMPTY ( .ColHeadings )
.ColHeadings = .ColNames
ENDIF
.Access = oDataTier.AccessMethod
oDataTier.CreateView ( .TableName )
ENDWITH
WITH THISFORM
FOR I = 1 TO .SearchFieldCount
IF RECCOUNT() > 0
.cmdSelect.Enabled = .T.
.Grid1.Visible = .T.
.Grid1.Column1.Alignment = 0
FOR I = 1 TO .SearchFieldCount
lcStrValue = TRANSFORM(EVALUATE(.KeyField))
.ReturnValue = lcStrValue
.Release
ENDWITH
WITH THISFORM
IF USED ( .ViewName )
USE IN ( .ViewName )
ENDIF
RETURN .ReturnValue
ENDWITH
CREATE FORM FindCustomer AS EachSearch FROM
Pinter
#End Region
End Property
End Property
End Property
End Property
End Property
#End Region
Handles cmdCancel.Click
KeyValue = ""
Hide()
End Sub
Handles cmdSelect.Click
End Sub
Handles cmdShowMatches.Click
If chkFuzzy.Checked Then
Fuzzy = "%"
Else
Fuzzy = ""
End If
If Ctrl.Name.ToUpper.StartsWith("SEARCH") Then If
Ctrl.Text.Length > 0 Then Cmd = Cmd + " AND " +
Ctrl.Name.Substring(6) ; + " LIKE '" + Fuzzy + Ctrl.Text
+ "%'"
End If
End If
Next
cn.Open()
cm.Connection = cn
ds.Clear()
da.Fill(ds, TableName)
If ds.Tables(TableName).Rows.Count = 0 Then
MsgBox("No records match",
MsgBoxStyle.Exclamation, Application.ProductName)
cmdSelect.Enabled = False Else
DataGrid1.DataSource = ds DataGrid1.DataMember =
TableName cmdSelect.Enabled = True End If
End Sub
End Sub
#End Region
o.LoadData() End If
Next
IF EMPTY ( THIS.Value )
RETURN
ENDIF
WITH THISFORM.Combo2
.Clear
SELECT 10
FOR I = 1 TO FCOUNT()
IF USED ( "THETABLE" )
USE IN THETABLE
ENDIF
SELECT Viewer
ZAP
WITH THISFORM
&Cmd
SELECT Viewer
ZAP
APPEND FROM DBF( [C1] )
GO TOP
USE IN C1
.Grid1.Refresh
ENDWITH
DO FORM FixData WITH ;
THISFORM.Combo1.Value,;
THISFORM.Combo2.Value,; Viewer.Data, ;
THISFORM.DataType ; TO Result
IF Result = [Changed]
THISFORM.cmdGo.Click
ENDIF
SELECT (Fld), COUNT(*) FROM (Table) GROUP BY
(Fld) ORDER BY 2 DESC
* Program-ID..: AUDIT.PRG
PARAMETERS Tbl
IF PCOUNT() = 0
SELECT 10
FOR I = 1 TO FCOUNT()
&Cmd
GO TOP
IF RECCOUNT() > 75
\<<PADL(TRANSFORM(EVALUATE(Fld)),50) + [ - ]
+ TRANSFORM(Kount)>>
ENDSCAN
ELSE
SCAN
\<<PADL(TRANSFORM(EVALUATE(Fld)),50) + [ - ]
+ TRANSFORM(Kount)>>
ENDSCAN
ENDIF
USE
SELECT ( Tbl )
ENDFOR
SET TEXTMERGE TO
SET STRICTDATE TO 0
SET STATUS BAR ON
SET TALK ON
SET SAFETY OFF
SET CPDIALOG OFF
StartTime = TIME()
Supervisor = 1
DataAccess = [SQL]
=SQLDISCONNECT(0)
ConnString = [Driver={SQLServer};Server=
(local);Database=Data;Uid=Les;Pwd=lp;]
AlienNum, ;
COUNT(*) ;
GROUP BY AlienNum ;
ORDER BY 2 DESC ;
IF VAL(laAliens(I,1)) = 0
LOOP
ENDIF
GO TOP
LOCATE FOR AlienNum = laAliens(I,1) FOR J = 1
TO laAliens(I,2) - 1
CONTINUE
DELETE NEXT 1
ENDFOR
ENDFOR
lcOldFile = [CONTACT]
ENDIF
APPEND FROM ( lcPath + lcOldFile )
GO TOP
ENDIF
REPLACE ALL ;
SELECT 0
USE IN CarryAll
USE
*(8) Convert all other tables
USE TABLIST
ExcludedList =
[,NOTES,MOVES,MEMBERS,CASES,ESL,PREMIUM,
SELECT 0
USE ( ALLTRIM(TABLIST.TableName) )
ZAP
APPEND FROM ( lcPath +
ALLTRIM(TABLIST.TableName) )
USE
SELECT TABLIST
ENDSCAN
USE
USE ( TName )
D = VARTYPE ( F ) O = ALLTRIM(laChanges[I,3]) N
= ALLTRIM(laChanges[I,4]) L = []
R = []
&Cmd
ENDFOR
ENDFOR
WAIT CLEAR
*(10) Upload all tables to SQL
USE TABLIST
ExcludedList = [,NOTES,FOXUSER,TABLIST,USERS,]
oDataMgr.MakeSQLTable ( [KeyFields] )
oDataMgr.Upload ( [KeyFields] )
Handle = SQLSTRINGCONNECT( s )
FieldList =
"CustomerID,CompanyName,ContactName,Region"
CURSORTOXML("SQLResult","lcXML")
USE
SQLDISCONNECT(0)
RETURN lcXML
ENDFUNC
FUNCTION CustsByCountry (pCountry AS String ) AS
String S = "Driver={SQL Server};Server=
(local);Database=Pinter;UID=sa;PWD=;"
Handle = SQLSTRINGCONNECT( S )
FieldList =
"CustomerID,CompanyName,ContactName,Country"
CURSORTOXML("SQLResult","lcXML")
USE
SQLDISCONNECT(0)
RETURN lcXML
ENDFUNC
FUNCTION OneCustomer (pCustID AS String ) AS
String S = "Driver={SQL Server};Server=
(local);Database=Pinter;UID=sa;PWD=;"
Handle = SQLSTRINGCONNECT( S )
RETURN lcXML
ENDFUNC
ENDDEFINE
IISRESET
! regsvr32 "XMLProject.dll"
o = CREATEOBJECT ( "XMLProject.XMLClass" )
? o.CustsByRegion("SP")
* Visual
FoxProWSDef :loName=http://localhost/WSDIR/Name
LOCAL loException, lcErrorMsg, loWSHandler
TRY
loWSHandler = NEWOBJECT("WSHandler",;
IIF(VERSION(2)=0,"",HOME()+"FFC\")+"_ws3client.vc
loXMLCLASS = loWSHandler.SetupClient( ;
"http://FRYS21/FoxWebServices/XMLCLASS.wsdl",
"XMLCLASS", ; "XMLCLASSSoapPort")
lcErrorMsg="Error:
"+TRANSFORM(loException.Errorno)+" -
"+loException.Message
DO CASE
CASE VARTYPE(loXMLCLASS)#"O"
lcXML = loXMLClass.CustsByCountry("Argentina")
XMLTOCURSOR(lcXML,"TEST")
BROWSE
<WebMethod()> Public Function CustsByRegion( _
End Function
End Function
ByVal e As System.EventArgs) _
Handles CountryCombo.SelectedIndexChanged
Dim ws As New
UseMyWebServices.localhost.MyWebService
DsByCountry1.Clear()
DsByCountry1.Merge(ws.CustsByCountry(CountryCom
DataGrid1.DataSource = DsByCountry1.Customers End
Sub
ByVal e As System.EventArgs) _
Handles RegionCombo.SelectedIndexChanged
Dim ws As New
UseMyWebServices.localhost.MyWebService
DsByRegion1.Clear()
DsByRegion1.Merge(ws.CustsByRegion(RegionCombo
DataGrid1.DataSource = DsByRegion1.Customers End
Sub
ByVal e As System.EventArgs) _
Handles CustCombo.SelectedIndexChanged
Dim ws As New
UseMyWebServices.localhost.MyWebService
DsOne1.Clear()
DsOne1.Merge(ws.OneCust(CustCombo.Text))
DataGrid1.DataSource = DsOne1.Customers End Sub
In the next chapter we'll see how similar FoxPro and Visual Basic .NET
are when it comes to designing and using reports. You've probably been
hearing about Crystal Reports for years, and you've probably heard some
horror stories. The latest version of Crystal is much, much improved. I
believe you'll not only like it, but even be impressed by both its power and
its ease of use.
Chapter 10. Reporting
IN THIS CHAPTER
FoxPro has long provided an excellent reporting tool. It's easy to use and
is completely integrated into the FoxPro IDE. Although it has often been
regarded as primitive, clever users have found ways to extend it beyond
anything the designers could have imagined.
or
either of which does the same thing. To print a report, you use the
command
[FOR lExpression1]
[WHILE lExpression2]
[HEADING cHeadingText]
[NOCONSOLE]
[NOOPTIMIZE]
[PLAIN]
[NAME ObjectName]
[SUMMARY]
The PREVIEW keyword lets users see their report before they decide
to print it.
The SUMMARY option prints only the summary lines; detail lines are
suppressed.
So you have one command to create the report and one command to
display the report. However, don't let the apparent simplicity fool you. For
many years, FoxPro has provided an easy and cost-effective way to
report on tabular data.
Internal Details
Reports in Visual FoxPro are stored in a pair of tables with the extensions
.frx and .frt. Because they're FoxPro tables, you can make changes
programmatically or manually to the contents of these tables. Hacking
system tables has its risks, but the open nature of FoxPro has long
endeared it to developers. So take the usual precautions to back files up
first, and you can do just about anything you can imagine. That's the
basis of GenRepoX.PRG, the amazing pre- and post-processing utility
written by Markus Egger. We'll look at GenRepoX later in this chapter to
give you some ideas. After that, it's up to you.
Report Layout in FoxPro
The FoxPro Report Designer has bands for Title, Summary, Header,
Footer, Group, and Detail bands. To design a report, you create a basic
layout, then add bands and grouping to correspond to data ordering or
indexing that will be done before the report is run. The resulting
interaction between the report file, the FoxPro report processor, and the
data is what gives you the end product.
When you create a new report, the only bands that are added are Page
Header, Detail, and Page Footer. A Report menu pad is added to the
FoxPro system menu _MSYSMENU, making additional options available.
In FoxPro 8 and higher, a Printer Environment selection has been added.
Also, two toolbarsReport Controls and Report Designerare available and
meaningful in the context of the Report Designer.
Page Setup
The default page layout of a FoxPro report is a single column, the width
of which is determined by the characteristics of your default printer. If you
select File, Page Setup from the FoxPro IDE menu to open the Page
Setup dialog, you can easily change a report to print two or more
columns, or change column margins. You can also tweak printer settings
and change the target device if you're building a report that must run on a
particular printer, for example, a dot matrix printer with NCR (no carbon
required) multicopy forms.
Within the Page Setup dialog, you can also select Print Setup and specify
a Landscape report if your printers permit it. If you print more than one
column of records, you can choose either row-major (left to right, top to
bottom) or column-major (top to bottom, left to right) order.
Printer Problems
FoxPro embeds the name of the printer that you choose, or the name of
your default printer if you don't choose one, into your reports. This leads
to one of the most annoying problems with the FoxPro Report Designer.
When FoxPro builds a report, it embeds the name of the printer for which
it was designed into the report layout. The report may not print properly if
a user tries to print on a different printer.
As a result, it's necessary to "hack" the report file and remove printer-
specific information before delivering it to users. To do that, type USE
(Reportname.FRX) and BROWSE the file. Then use Ctrl+PgDn to open
the Expr field in the first line (the one that has ObjType = 1 and
ObjCode = 53) and remove the contents of the Expr, Tag1, and Tag2
fields. If you open and save the file again in the Report Designer, you'll
have to do this again. You might want to write a little program to open all
of your reports and search for this problem.
Report Controls
In each band, you can drag and drop any of the report controls onto the
report. Controls include labels, fields, lines, rectangles, rounded
rectangles, and ActiveX (OLE) bound controls (for example, pictures).
Labels are for displaying text that never changes. Report controls are a
little different from form controls. On a form, you might use a label to
display values of variablesa report title or a group heading. In the Report
Designer, labels are constants. When you put one on a form, you
immediately start typing the label's caption. You can't assign it a different
caption later.
Fields don't have to have a tablename as a prefix, and in fact, it's often
the last thing you want to do. In the past, FoxPro developers had the
habit of dragging tables into the Data Environment, then onto the screen.
This stored a hard-coded reference to both the tablename and the field
name into the ControlSource of each field. If you subsequently wanted
to use a SQL SELECT statement to get the data, you'd have to go
through the properties of the fields in the report and remove "FieldName."
from each ControlSource. Because multiple field properties are stored
in a single memo field, there's no really easy way to automate this
process. It's easier to avoid the problem than to have to repair it later. So
I recommend making a habit of entering expressions without a table
prefix.
Lines and rectangles can be used to make a plain report look crisp and
professional. If you put a line above each group heading, groups are set
off in a way that's clearer for users to understand. Rectangles that start in
the Page Header band and end in the Page Footer band, or rectangles
that start in the Group Header band and end in the Group Footer band,
automatically expand to include detail bands within them. Nested
rectangles for pages and groups, separated by three or four pixels,
create a very professional look with little effort.
From the Report menu, select the Title/Summary pad. You'll see the
dialog shown in Figure 10.2.
Figure 10.2. The Title and Summary band dialog.
The Title and Summary bands, if requested, print only once. As you might
surmise, the Title page prints at the beginning of the report, and the
Summary prints at the end of the report. The only option for these bands
concerns your decision to print title and summary pages as separate
pages. If they're small, they probably shouldn't be printed separately.
They'll just appear at the top of the first page and the bottom of the last
page, respectively.
Data Grouping
If you select Data Grouping from the Report menu pad or from the Report
Controls toolbar, you can take advantage of any data sorting that might
have been done as of the moment the report runs. If you select Data
Grouping, you'll see the dialog shown in Figure 10.3.
Group expressions are presumably fields in the report data that have
been used in ORDER BY expressions, or are part of the active index
tag(s). Generally, these expressions mirror the current order. For
example, if your SELECT statement was
However, you can include the name of a report variable that's calculated
as the records are processed. For example, suppose you add a variable
called StateName and define it using the following expression:
If you then use StateName as the Group By variable, you won't see
any breakdown of departments, provinces, or other subnational
groupings except for customers in the United States.
Variables
For the examples in this chapter, we'll use the tables from the FoxPro
sample data directory (HOME() + "Samples\Data\"). Because
reports are read-only, you can issue the command
to ensure that your programs can find the tables when you USE them.
Start FoxPro and open the Customers table. Type this command:
This creates a primitive report with one column for each field in the
current alias until the fields no longer fit. Obviously, it's a pretty primitive
approach, but it works. You can specify which fields to include by adding
FIELDS FieldList where FieldList contains field names to include,
separated by commas, after the word COLUMN. Here's an example:
Adding Groupings
Usually you would create groupings of tabular data that render it more
meaningful. For example, let's say you create a cursor of Orders for last
month, including customer name from the Customers table, from the
Solutions\Data directory in the Samples\Data subdirectory of the
FoxPro home directory (see Listing 10.2).
Customer.Country, ;
Customer.Region, ;
Customer.Company ;
INTO CURSOR C1
Calculated Expressions
Because grouping often goes hand in hand with calculating counts and
totals, when you put a field on a summary line, you can right-click on it,
select Properties, and select a calculation to be performed on the field
that is the object of the control. For example, if the rightmost column in
each detail row is the order_amt for each order, it would be useful to
copy and paste the order_amt field to the group footer line, placing the
copy exactly below the original order_amt field. Open the Calculate
Field dialog shown in Figure 10.6 to select the calculation type (sum,
count, and so on). Note that you need to reset totals to zero whenever
the grouping variable changes in value.
Figure 10.6. Calculated data group band fields.
The report layout shown in Figure 10.7 shows a report with subtotals by
region within country.
You're not limited to counts and totals: If you want to display the highest
order amount by country, you declare a variable called MaxOrder, set it
to MAX(Orders.Order_Amt), and include it on the Group Summary
band for Country. The option to reset when Country changes completes
the calculation.
I also want to do something about the region totals for countries that don't
have a region entry. When we added a second group using the Region
variable, we got a group header and a group summary line that has the
name of the region. However, the data appears not to have region entries
in most cases. Double-click on each of the entries on the Region Header
and Region Footer to open the dialog shown in Figure 10.11.
Figure 10.11. Suppressing printing of region headers and
footers when region is blank.
Click on the Print When button, enter Region <> "" in the text box with
the heading Print Only When Expression Is True, and check the Remove
Line If Blank check box. If you do this for all of the fields and labels in the
group header and footer for Region, it will indeed disappear except for
countries that have named regions.
What if you want to show states only for the United States? If you create
a new report variable named, say, m.StateName, specify that it's a blank
string if Country is not "USA", and do the group based on it, you won't get
a subtotal for countries other than the USA because m.StateName will
be blank. The Group Summary line only fires if the value changes.
Report Filtering
The Init event of the Data Environment of a FoxPro report form is the
place where data for the report has to be acquired. If at the end of the
Init event, your user can't connect to the data source, can't find the
tables, or for whatever reason doesn't want to continue, the statement
RETURN .F. will cancel the reporting process without printing. For this
reason, the Init event of the report's Data Environment is a good place
to put a report filtering mechanism.
The form must be a modal form so that it can return a value. Listing 10.3
shows the Click event code for the Show Matching Records button.
WITH THISFORM
Exp1 = []
Exp2 = []
Exp3 = []
Exp4 = []
+ ALLTRIM(LOWER(.Text1.Value)) + [%']
ENDIF
+ ALLTRIM(LOWER(.Text2.Value)) + [%']
ENDIF
+ ALLTRIM(LOWER(.Text3.Value)) + [%']
ENDIF
IF .List2.ListCount <> 0
ENDFOR
ENDIF
+ Expr
ENDWITH
MESSAGEBOX( TRANSFORM ( ;
cmdSelect::Click
WITH THISFORM
.ReturnValue = .T.
.Release
ENDWITH
cmdCancel::Click
WITH THISFORM
.ReturnValue = .F.
.Release
ENDWITH
Unload event code:
IF USED ( [Orders] )
USE IN Orders
ENDIF
IF USED ( [Customer] )
USE IN Customer
ENDIF
RETURN THISFORM.ReturnValue
Back in the report, all you have to do is put the code from Listing 10.5 in
the form's Init code.
DO FORM CustFilter TO Ok
IF NOT Ok
IF USED ( [ReportCursor] )
USE IN ReportCursor
ENDIF
RETURN .F.
ENDIF
An additional benefit of using a report filter form of this type is that with
only a few changes you can use this form with SQL Server or with other
data sources. I won't show you any of the others because I've only heard
of a very few cases of people using the FoxPro report writer with data
sources other than DBFs and SQL Server. But for SQL Server, it's very
easy. All you have to do is make the changes shown in Listing 10.6 to the
cmdSelect::Click event code.
WITH THISFORM
Exp1 = []
Exp2 = []
Exp3 = []
Exp4 = []
+ ALLTRIM(LOWER(.Text1.Value)) + [%']
ENDIF
+ ALLTRIM(LOWER(.Text2.Value)) + [%']
ENDIF
+ ALLTRIM(LOWER(.Text3.Value)) + [%']
ENDIF
IF .List2.ListCount <> 0
FOR I = 1 TO .List2.ListCount
ENDFOR
ENDIF
+ Expr
IF lr < 0
MESSAGEBOX( TRANSFORM ( ;
ENDWITH
I had to remove the INTO CURSOR C1 clause because the name of the
cursor is supplied as the optional third parameter in the SQLExec()
function call.
You could build the form so that it could be used with either SQL or with
FoxPro tables, using a form property to determine whether to use macro
expansion to run the FoxPro SELECT command, or to call the
SQLExec() function. In either case, the result is a cursor, and REPORT
FORM doesn't care.
There is one other consideration when you work with SQL Server. It's not
uncommon to include a date, or a range of dates, in a report filter form. In
FoxPro, the date delimiters are braces, like this:
ldDate = {03/15/2004}
In SQL, dates are delimited with single quotes. So if your filter form
includes a pair of date fields, the logic for building an expression based
on them would have to delimit the resulting date strings with single
quotes instead of with braces.
Finally, the only delimiter allowed for strings in SQL is the single quote.
So your expressions must use single quotes to delimit strings. Because
of this, if your users type in expressions that contain a single quote, for
example, "Frank's Franks", you have to replace the single quote in their
entry with either a pair of single quotes or a replacement character. And
because you don't know when they might do this, you have to do so
every time. CHR(146) is a passable substitute for the offending
CHR(39):
+ CHRTRAN(ALLTRIM(LOWER(.Text1.Value)),['],CHR(14
Printing Tricks
A few of the things you can do with FoxPro reports are less than intuitive.
For example, to print "Page 1 of NN," you actually have to run the report
twice (see Listing 10.7). The trick is that _PageNo is a system variable; at
the end of a report, it still contains the last page number printed. You can
then refer to the saved variable in a field expression in your report.
nPages = 0
WAIT WINDOW 'Getting page count' NOWAIT
nPages = _PAGENO
ERASE (FName)
Generic Reporting
The fact that FoxPro reports are just tables is what made this possible. It
also made possible GenRepoX, a fabulous utility written by Markus
Egger, which extends the FoxPro report-writing capability considerably.
When you've designed a nice report, you might want to publish it on the
Internet. If the report is one that seldom changes, it's ridiculously easy.
Just publish the output of the report as HTML, put the resulting text files
into a virtual directory, and send your users links to the filenames.
Listing 10.8 shows the code to build SimpleReport1 as a Web page that
is written to a previously determined filename in a virtual directory.
SET CONFIRM ON
RETURN
ENDIF
SELECT ;
ORDERS.Order_Amt, ;
Customer.Company, ;
Customer.Region, ;
Customer.Country ;
ORDER BY ;
Customer.Country, ;
Customer.Region, ;
Customer.Company ;
INTO CURSOR C1
IF RECCOUNT() = 0
USE IN C1
RETURN
ENDIF
\<html><head><title>Customers by country</title></head
\<body>
\<table>
\ <tr><td width="100">
\ <tr><td size="1"><<LEFT(TIME(),5)>></td></t
\ </table>
\ <td width="600" size="4" align="center" valign="
\ </tr>
\<table>
LastRegion = "X"
LastCountry = "X"
\<table>
SCAN
\<tr><td>Region: <<Region>></td></tr>
ENDIF
\<tr><td>Country: <<Country>></td></tr>
ENDIF
* Each row in the table is another small two-column ta
\<tr><td><table><tr><td width="80%"><<Company>></t
\\<<TRANSFORM(Order_Amt,"#,###,###.##")>></td>
\ </tr>
\ </table></td>
\</tr>
LastCountry = Country
LastRegion = Region
ENDSCAN
\</table>
USE
SET TEXTMERGE TO
This is another easy way to build report pages for publication on the
Internet, and it gives you additional control over formatting that you can't
get with REPORT FORM and SAVE AS HTML.
This would be fine for relatively static information, like a company phone
directory. However, it still doesn't do what you usually want to do, which is
to format and send output directly to the Internet at the moment the user
asks for it. That requires an Internet application. Internet applications
have to know how to interact with Internet Information Server (IIS), and
that's a little more involved. In the last chapter, we'll explore Internet
database application development in more detail.
Exporting to a PDF
Data searching
Subreports
Hyperlinks
Integrated graphing
If any of these features are important, you can simply use Crystal
Reports, which integrates perfectly with FoxPro. It costs about $500,
which means that it will probably pay for itself the first day you use it. The
runtime that will be needed by users of your application is royalty-free.
NOTE
Reporting is a huge topic, and I've only scratched the surface. Cathy
Pountney has written an excellent book on the subject (The Visual
FoxPro Report Writer, ISBN 1-930919-25-5, www.Hentzenwerke.com).
Reporting in Visual Studio .NET
Crystal Reports is a third-party product licensed by Microsoft and
provided as part of Visual Studio. A separate version (Version 9 as of this
writing) is available from Crystal Decisions, and as you might imagine, it
has features not included in the Visual Studio version. But the version
that's included with Visual Studio has pretty amazing capabilities, and
you may never need more than what comes right out of the box.
Let's start with a simple report from the Customers table of the Northwind
database. Start by creating a Windows application called SimpleReport1.
In the code to accompany this book, it's in the Chapter10Code
directory. Right-click on the project name in the Solution Explorer, choose
Add, Add New Item from the context menu, and then select Crystal
Report from the available choices. Call the report SimpleReport1.rpt
and click on Open.
From the list of available templates, you'll begin to see the range of
possible reports that you can build. The defaults, Use the Report Expert
(wizard) and Standard, are fine for now. Click on OK to continue.
For the data source, select OLE DB(ADO), and then pick Microsoft OLE
DB Provider for SQL Server. Next, fill in the Connection Information
dialog with the server name (local), the user ID (sa), the password
(blank), and the Database name (Northwind). (If you need to use a
different UserID and password, use them here.) You don't need to
change anything in the Advanced Information dialog that follows in most
cases. Expand the tree view to show the table names and select
Customers.
By this time, the Standard Report Expert dialog will look like Figure
10.13.
Click Insert to select the Customers table, and you'll advance to the next
dialog, which lets you pick the fields to be included in the report. Double-
click on the CompanyName, ContactName, City, Country, and Phone
fields to select them, as shown in Figure 10.14.
Figure 10.14. Selecting fields for your report.
Grouping is a desirable feature in many reports. It's very easy and shows
off a few cool Crystal Reports features. Select Country as the Group By
field as shown in Figure 10.15 and click Next.
Finally, you'll see that a graph has been created based on your selection
of subtotals including counts. It's free, although you can suppress it if you
want. Finally, when the dialog appears to name the report, enter
"Customers by country" as the report title and click Finish.
The designer will display your report at this point, as seen in Figure
10.16.
Handles ComboBox1.SelectedIndexChanged
CrystalReportViewer1.ReportSource = Nothin
Case "SimpleReport1"
CrystalReportViewer1.ReportSource = New Si
End Select
End Sub
LogonInfo = Tab.LogOnInfo
With LogonInfo.ConnectionInfo
.ServerName = "localhost"
.UserID = "sa"
.Password = ""
.DatabaseName = "Northwind"
End With
Tab.ApplyLogOnInfo(LogonInfo)
Next Tab
If you want to offer users options at this point, all you have to do is drag a
ReportDocument from the Components toolbox onto the form and
assign the strongly typed report class to it. Figure 10.18 shows the dialog
that appears when you put the ReportDocument on your form.
Handles ComboBox1.SelectedIndexChanged
CrystalReportViewer1.ReportSource = Nothin
Case "SimpleReport1"
End Select
End Sub
Use Ctrl+Alt+S to open the Visual Studio Server Explorer. The Server
Explorer can be used to create connections, create databases, add
tables, and generally do many of the things that we've always been able
to do in FoxPro, but which required the Enterprise Manager in SQL
Server. MSDE didn't have a user interface, which made using MSDE
relatively difficult (a nice way of saying "not worth the trouble"). The
Server Explorer fixes that and provides many other features as well.
One of them is something you might have noticed if you've opened the
Server Explorer before. If you expand the Servers node, the first entry is
Crystal Services. And one of the nodes directly below Crystal Services is
Server Files. Server Files shows the contents of a directory in your Visual
Studio directory tree; on my computer it's this:
ServerFileReport1 [CrystalDecisions.Shared.ServerFileR
Now that you know how easy it is to populate a dataset, you'll be pleased
to know that you can design your report to get its data from an ADO.NET
dataset instead of from a database using an OLE DB provider.
If you click on the Show All icon at the top of the Solution Explorer
window, you'll see that under the dsCusts.xsd file, there are two other
files named dsCusts.vb and dsCusts.xsx. These three files
constitute a typed dataset.
First you'll need to create a report using this dataset. Select Add New
Item, Crystal Report from the Solution Explorer right-click context menu.
In the resulting Standard Report Expert dialog, on the Data page, click on
the Database Files node of Available data sources. You'll be given a
chance to browse to your new dataset, dsCusts.xsd. Click on it and
continue with the steps to design the same report layout we designed
earlier.
Next, open the new report and right-click on its design surface to get a
context menu, then select Database, and then from the submenu select
Set Location. In the Set Location dialog, expand the Project Data node,
the ADO.NET DataSets node, the node for your dataset, and select the
Customers table (see Figure 10.20).
Now that we have a report to run against dsCusts, drag the Customers
table from the Server Explorer onto the designer surface of the form. This
will add SQLConnection and SQLDataAdapter objects to the form that
points to the Northwind database and its Customers table, respectively.
Rename these objects scnNorthwind and sdaCustomers, modify the
ConnectionString property of scnNorthwind to include a valid user
ID and password, and then right-click sdaCustomers and select
Generate Dataset. In the Choose a Dataset section, select the Existing
radio button, and the class name of your strongly typed dataset from the
combo box (see Figure 10.21).
Figure 10.21. Pointing to the dataset.
Click the OK button and add the following code to the Load event:
sdaCustomers.Fill(DsCusts1, "Customers")
datasetReport1.setdatasource(DsCusts1)
CrystalReportViewer1.ReportSource = datasetReport1
How It Works
If you change the Startup page of the project to DatasetReport and press
F5 to run it, you'll see that the report looks the same as before. However,
this time the data doesn't come straight from a database, but rather from
a DataSet object. The DataSet was populated by a SELECT statement
from the Customers table. The SELECT statement and the connection
string could have referenced any server and any tablename, as long as
the dataset has the same structure. The dataset is like a FoxPro cursor;
it's just a container for the data. The Fill method of the DataAdapter
just pours the data into it.
Clicking on the plus sign when you haven't yet selected a data source
produces the OLE DB Provider Selection Expert (wizard), as shown in
Figure 10.23. You'll want to select Microsoft OLE DB Provider for SQL
Server, as shown in Figure 10.23.
The next step, though, is somewhat unexpected. You might imagine that
you would go to the Select tab and enter parameters. The Select tab is
used to enter hard-coded selection criteria for building a SELECT
statement without any parameters. If you want the user to supply the
parameter, there's another way.
You can also click on the Toggle Field View icon, the first
one in the Crystal Reports toolbar, to show or hide the Field
Explorer.
Select New, and you'll get the dialog shown in Figure 10.29. Enter
mCountry for the parameter name, Country to include as the
prompt, and String as the type. There are a few more options, but you
probably won't use this except for prototyping, so don't get too excited.
Figure 10.29. Specifying the new parameter.
The mere fact that you have a parameter doesn't do anything. You have
to use it in a selection formula. To do this, right-click anywhere on the
form that's not a field or a label, and select Report, Edit Selection
Formula, Records from the context menu, as shown in Figure 10.30.
Now build the project. Open a browser and type the following URL:
http://localhost/SimpleReportWebService1/SimpleReport1
Refresh
FindText
GetPage
GetTotaller
TestReport
GetGroupLevelData
FindGroup
GetReportInfo
Export
You can't test them in the Web page because they're meant to be used
from within a Windows form to manipulate and return elements of the
report. The ReportViewer control knows what to do with them, and
that's what matters.
Handles CrystalReportViewer1.Load
With CrystalReportViewer1
.ReportSource = "http://localhost/SimpleReportWebSer
+ "SimpleReport1Service.asmx"
End With
End Sub
You can add a Web reference for this .asmx file to the project. Having
done this, you can assign a new instance of the Web Service class to the
ReportSource:
In order to make this look easy, however, the folks at Crystal Decisions,
or perhaps in Redmond, have done a lot of work. Essentially, two classes
have been created in the codebehind file for SimpleReport1.rpt:
...
The first one is the primary class for implementing the strongly typed
report. It inherits from ReportClass and wraps various members of the
ReportDefinition.Sections collection. The second one
implements the ICachedReports interface. It creates a cached report.
In its CreateReport method it creates an object based on the
SimpleReport1 class. On the Web Service side, there are also two
classes, SimpleReport1Service, which inherits from
ReportServiceBase, which exposes the methods that publish the
report as a Web service, and CachedWebSimpleReport1, the caching
counterpart.
When this .asmx file has been generated, all you have to do is copy it,
together with the related RPT file, to your Web Services directory.
The good news is that thin client applications are simple. The bad news
is that thin client applications are simple. For simple applications, they're
not hard; for complex applications, they're not appropriate. That about
covers the controversy.
However, the one area that seems to be an excellent fit for using Internet
browsers, either on the Internet or on an intranet, is reporting. Reporting
is one-way, which is perfect for use with a browser.
Create a new ASP.NET Web Application in Visual Studio .NET, and use
the Add Existing Item feature to add SimpleReport1.rpt to the
project. Make sure that the report is looking to the Customers table for its
data.
CrystalReportViewer.ReportSource = Server.MapPath("Sim
Run the application and you'll see that it works as before. The output sent
to the browser is, of course HTML; but search, navigation, zoomall of the
features except printingare supported through the use of trips back to the
server.
Exporting to a PDF
If you want users to be able to print a copy of the report, browser output
is usually not a very good option. A better one is to export your report to
Adobe Acrobat (PDF) format. You simply export the report to a PDF file
and redirect the browser to that same file. Listing 10.13 shows the code
to do this.
CrystalDecisions.Shared.DiskFileDestinationOptio
Dim FName As String = GUID.NewGUID().ToString
destopts.DiskFileName = FName
.ExportOptions.ExportDestinationType = _
CrystalDecisions.Shared.ExportDestinationTyp
.ExportOptions.ExportFormatType = _
CrystalDecisions.Shared.ExportFormatType.Por
.ExportOptions.DestinationOptions = destopts
.Export()
End With
Response.Redirect(FName)
End Sub
Because several users may request the report at the same time, you
need to provide unique names for PDF files. I used the NewGUID()
method of the GUID class that comes with .NET. (I had to use the
ToString cast because technically, a GUID is a type and a string is a
different type.)
Standard
Form Letter
Form
Cross-Tab
Subreport
Mail label
Drill Down
We have one more thing to do. If you consider that the form letter will try
to fit multiple objects on a single page if they're small enough to fit, you'll
understand why you need to either expand the detail band to 9 inches in
height or do something else. Something else is preferable in this case;
right-click on the Detail Band and you'll see the Properties sheet shown
in Figure 10.34. Check the New Page After option, and you'll get exactly
one letter per page.
Figure 10.34. Forcing a new page after the detail band for a
record prints.
Finally, go to MainForm and add the following two lines of code for the
mnuFormLetter menu code:
frm.Show()
CAUTION
There's a gotcha here that got me, so I'll try to save you the
pain: Don't name your form and your report both FormLetter,
even though they have different file extensions. The reason
for those endless object prefixes in the Visual Studio .NET
world (besides the fact that because a form in Visual Studio
.NET just has an extension of .vb or .cs, you can't tell if a
file is a form or not without the prefix) is that the filename is
usually used as the default class name. Because both the
report and the form will be converted to classes, you'll end
up with two FormLetter classes.
The report then asks you to select a table. The Products table has some
useful fields to tabulate, so I'll use it. Pick the Supplier ID and Category
ID as the row and column tabs. But there's no meaningful value to add;
adding quantities of different items is literally adding apples and oranges.
In Figure 10.37, I've entered the two control fields at the top and left, and
the value to be totaled in the Summarized Fields box.
It's easy to create links in Web pages that pass parameters to other
pages in such a way as to give the illusion of "drilling down" into the data.
This feature is one that users are accustomed to, and ask for in our
applications. Unfortunately, it's not as easy to do in Windows Forms
applications as it is in a Web page. But Crystal Reports' Drill Down Expert
builds reports that do just that. I've added a new form called frmDrilldown
with a CrystalReportViewer control on it, docked to all sides as
usual. In Figure 10.39, I've added a Crystal Report to the project, and
selected the Drill Down expert.
I added the usual database connection, then selected the Customers and
Orders tables. The expert correctly linked the two tables on
CustomerID. I added the Customers table and the Orders table, and the
expert correctly linked the two using their CustomerID fields (see Figure
10.40).
Figure 10.42. The Drill tab: Show the Customer fields, hide
the Orders fields.
Frm.Show()
The resulting report layout is shown in Figure 10.44. Group and detail
records share the same column headings. It looks strange when you first
run the report because the column headings for the detail records show
even though the detail records don't.
In the next chapter, we'll look at the competition: thin client. It's important
to know what it is and how it works because many of your competitors
will try to sell thin client applications as the solution in cases where a
smart client (the same kind of applications we've been developing for
years, but using an Internet-based data server) would better serve their
needs. You need to understand how they work in order to understand
their considerable shortcomings.
[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N]
[O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]
[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N]
[O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]
.dll files
Visual FoxPro creation
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
ACCESS methods
versus setters 2nd
accessing
SQL Server
http data source settings
http general settings
http security settings
SQL Server via XML 2nd 3rd
ActiveX controls (Screen design) 2nd
Add buttons
Click event codes
creating in forms 2nd 3rd 4th
Add Cursors page (CursorAdapater Builder) 2nd
adding
buttons to forms
VB .Net applications 2nd
code
to events
connections
to forms 2nd
Crystal Reports
to other servers in VS .NET Server Explorer
datasets to XML Web services 2nd
grids to forms
groupings to reports
in VFP Report Designer
internet data access
to Data Tier programs 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
parameters to parameterized reports
in Crystal Reports 2nd
records
to database applications
records to forms
records to SQL
selection formulas to parameterized reports
in Crystal Reports 2nd
variables
to post buffers
Web references
to Web Services
adding/removing
menus
in VFP
AddPostKey methods
AddTableSchema method (XMLAdapter class)
ADO.NET
DataAdapter creation
displaying properties 2nd
properties
declaring variables
Adobe Acrobat
VFP reports
publishing to Internet
Alias property (CursorAdapter class)
aliases (tables) 2nd
AllowDelete property (CursorAdapter class)
AllowInsert property (CursorAdapter class)
AllowUpdate property (CursorAdapter class)
APP files
running
in Visual FoxPro
Visual FoxPro versus Visual Basic .NET 2nd
app servers
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
APPEND BLANK command
phantom records 2nd
APPEND BLANK commands
applications
menus
hotkeys
positioning in VFP
VB .NET creation 2nd
VFP creation 2nd 3rd
startup screens
VB .NET creation 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
15th 16th 17th 18th 19th 20th 21st 22nd 23rd
VFP creation 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th
29th 30th 31st 32nd 33rd 34th 35th 36th 37th 38th 39th 40th 41st
VB .NET creation
adding buttons to forms 2nd
adding command buttons to forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
11th
adding controls to forms 2nd
adding searches to forms 2nd
AppScreen form configuration 2nd
BaseForm Load events 2nd
button Click event codes 2nd 3rd 4th 5th
class library creation 2nd
coding form classes 2nd 3rd 4th
Customer table creation 2nd 3rd
dataset creation 2nd
declaring property variables
displaying properties 2nd
displaying table records 2nd
form creation 2nd
form design 2nd
form methods 2nd
inheritable form creation 2nd
Inputs subroutines 2nd
LoadList-Click routines
menu creation 2nd
project names
project type/language selection
property procedures 2nd
using form templates 2nd
Windows Controls libraries 2nd 3rd
VFP creation
adding command buttons to forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
11th 12th 13th
Customer table creation 2nd
data tier class library creation 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
12th
default setting changes
enabling/disabling form controls 2nd
Field Mapping form controls
form controls class libraries 2nd
form creation 2nd
form template creation 2nd 3rd 4th 5th 6th
form templates 2nd 3rd 4th 5th 6th 7th
primary keys 2nd
running applications 2nd
running forms 2nd
search form templates 2nd 3rd 4th 5th 6th 7th
SQL database creation 2nd 3rd 4th 5th
SQL Servers 2nd 3rd
testing table data 2nd
three-tier data access
writing applications 2nd 3rd 4th 5th
AppScreen forms
property configuration 2nd
arrays 2nd
asmx files
XML Report Web Services 2nd
ASP (Active Server Pages) 2nd
ASP .NET reporting clients
Crystal Reports 2nd
exporting as PDF files
in Crystal Reports 2nd
ASSIGN methods
versus getters 2nd
assigning
providers to connections
Attach method (XMLAdapter class)
attaching indexes
to DBF files 2nd
attributes (XML elements)
Audit screen (VFP search forms) 2nd
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
backups
SQL
VFP
BaseForm Load events
VB .NET application creation 2nd
BindingContext references
data binding
in VB .NET
BOM (Byte Order Mark)
XML files
Bookmarks
Visual FoxPro versus Visual Basic .NET
BOTTOM commands
breakpoints
setting
in Virtual Basic .NET
Browse buttons
creating in forms 2nd 3rd
buffering
VFP tables
phantom records 2nd
TableRevert() functions
TableUpdate() functions 2nd
BufferModeOverride property (CursorAdapter class)
building
app servers 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
class libraries
VFP versus VB .NET 2nd
DataAdapter
forms
in VB .NET applications
in VFP 2nd
inheritable forms 2nd
INSERT statements (SQL) 2nd 3rd 4th
Internet applications 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
adding Internet data access to Data Tier programs 2nd 3rd 4th 5th 6th
7th 8th 9th 10th 11th 12th
app servers 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
application form button functions 2nd
Customer forms 2nd 3rd
EditCustomer forms 2nd 3rd 4th 5th 6th
Internet servers 2nd 3rd 4th 5th
key fields as user-specified strings
Web services 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
15th 16th 17th 18th 19th 20th
Internet servers 2nd
menus
in VB .NET applications 2nd
parameterized reports
in Crystal Reports 2nd 3rd 4th 5th 6th 7th
PRG
reports
in Crystal Reports 2nd 3rd 4th
simple applications in VB .NET
adding command buttons to forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
11th
Customer table creation 2nd 3rd
datset creation 2nd
form creation
form design 2nd
form methods 2nd
simple applications in VFP
adding command buttons to forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
11th 12th 13th
Customer table creation 2nd
default setting changes
enabling/disabling form controls 2nd
Field Mapping form controls
form controls class libraries 2nd
form creation 2nd
form template creation 2nd 3rd 4th 5th 6th
running forms 2nd
UPDATE statements (SQL) 2nd 3rd 4th
VB .NET applications
adding buttons to forms 2nd
adding controls to forms 2nd
adding searches to forms 2nd
AppScreen form configuration 2nd
BaseForm Load events 2nd
button Click event codes 2nd 3rd 4th 5th
class library creation 2nd
coding form classes 2nd
declaring property variables
displaying properties 2nd
displaying table records 2nd
form creation
inheritable form creation 2nd
Inputs subroutines 2nd
LoadList-Click routines
menu creation 2nd
project names
project type/language selection
property procedures 2nd
selecting inheritable classes for inheritable forms 2nd
using form templates 2nd
Windows Controls libraries 2nd 3rd
VFP applications
data tier class library creation 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
12th
form templates 2nd 3rd 4th 5th 6th 7th
primary keys 2nd
running applications 2nd
search form templates 2nd 3rd 4th 5th 6th 7th
SQL database creation 2nd 3rd 4th 5th
SQL Servers 2nd 3rd
testing table data 2nd
three-tier data access
writing applications 2nd 3rd 4th 5th
Web services
in VB .NET 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
in VFP 2nd
WSDL files
XML code in DOM 2nd
XML Report Web Services 2nd
buttons
Add
Click event codes
Add button
creating in forms 2nd 3rd 4th
adding to forms
VB .NET applications 2nd
Browse button
creating in forms 2nd 3rd
Cancel
Click event codes
Cancel button
creating in forms 2nd
Clear button (VFP search forms) 2nd
Close
Click event codes
color
command buttons
adding to forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th
properties 2nd
Delete
Click event codes
Delete button
creating in forms 2nd 3rd
Edit button
creating in forms 2nd 3rd
naming
Next button
creating in forms 2nd 3rd
positioning
Previous button
creating in forms 2nd 3rd
Save
Click event codes
Save button
creating in forms 2nd 3rd
saving as classes 2nd
Select button (VFP search forms)
Show Matches button (VFP search forms) 2nd
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
calculated expressions
VFP reports 2nd 3rd
calling
functions
methods
procedures
Web services via XMLDOM
Cancel buttons
Click event codes
creating in forms 2nd
CDX files
changing
form code to class code 2nd
character keys
check boxes
color
class browser
Visual FoxPro versus Visual Basic .NET
class libraries
form controls class libraries
VFP application creation 2nd
VFP construction
versus VB .NET construction 2nd
Windows Control libraries (VB .NET application creation) 2nd 3rd
Class View
Visual FoxPro versus Visual Basic .NET
Class View window
Visual FoxPro versus Visual Basic .NET 2nd
Class View window (Solution Explorer)
viewing typed datasets 2nd
classes
changing form code to 2nd
coding form classes
VB .NET applications 2nd
creating objects
CursorAdapter 2nd 3rd
finding
Reading XML 2nd 3rd 4th
CursorAdapter class 2nd
manually coding 2nd 3rd
methods 2nd
properties 2nd 3rd 4th 5th
data access classes
events 2nd 3rd 4th 5th 6th 7th 8th
declaring events in 2nd
EasySearch class
VB .NET search forms 2nd 3rd 4th 5th 6th
VFP search forms
EasySearch template class code 2nd 3rd 4th 5th
events
FlatFileForm template class code 2nd 3rd 4th 5th
inheritable classes
selecting for inherited forms 2nd
methods
namespace declarations 2nd 3rd
namespaces
Imports statements 2nd
PRG
adding methods to
PRG storage 2nd
properties 2nd
saving buttons as 2nd
saving forms as 2nd 3rd
subclassed screen control classes
editing 2nd
subclasses
properties 2nd
VB .NET base control classes 2nd
VCX
adding methods to
VCX storage
VFP base control classes 2nd
VFP versus VB .NET
XMLAdapter
AddTableSchema method
Attach method
LoadXML method
ToXML method
XMLAdapter class 2nd
converting VFP tables to XML
converting XDR schemas to XSD schemas 2nd
cursor creation 2nd
diffgram creation 2nd
receiving/processing diffgrams
updating cursors 2nd
Web Service creation 2nd 3rd 4th
clauses
Handles clauses
events
Implements 2nd
cleaning up
VFP search form data 2nd 3rd 4th 5th
Clear button (VFP search forms) 2nd
Click events
Add buttons
Cancel buttons
Close buttons
Delete buttons
Save buttons
Close buttons
Click event codes
closing
forms 2nd
handles
programs
cmdAdd.click template codes
code
snippets
accessing in Virtual Basic .NET
code window
Visual FoxPro versus Visual Basic .NET 2nd
coding
CursorAdapter class 2nd 3rd
form classes
VB .NET applications 2nd
collections
ColNames property (VFP search forms)
color (Screen design) 2nd
buttons
color (screen design)
check boxes
color (Screen design)
form controls 2nd
graphical style controls 2nd
color-coded grids (Screen design)
column sorting
in record lookup grids (VFP) 2nd 3rd 4th 5th
columns
data binding
columns (reports)
customizing
in VFP Report Designer
ColWidths property (VFP search forms)
command buttons
adding to forms 2nd 3rd
Add button 2nd 3rd 4th
Browse button 2nd 3rd
Cancel button 2nd
Delete button 2nd 3rd
Edit button 2nd 3rd
Next button 2nd 3rd
Previous button 2nd 3rd
Save button 2nd 3rd
properties 2nd
command windows
prior commands, executing
Visual FoxPro versus Visual Basic .NET 2nd 3rd
commands
APPEND BLANK
phantom records 2nd
BOTTOM
CREATE CURSOR
migrating VFP search form data to SQL server
DELETE
FCOUNT()
FIELD
GO BOTTOM
GO TOP
inserting
in CursorAdapter 2nd 3rd 4th 5th
RECCOUNT()
RECNO()
SCAN...ENDSCAN
SELECT
VFP tables
Select Case
SET FIELDS (VFP)
record filtering
SET FILTER (VFP)
record filtering 2nd
SKIP
SQL commands
handles
SQLCANCEL()
SQLCOLUMNS()
SQLCOMMIT()
SQLConnect()
SQLDisconnect()
SQLExec()
SQLGETPROP()
SQLMORERESULTS()
SQLPREPARE()
SQLROLLBACK()
SQLSETPROP()
SQLStringConnect
SQLTABLES()
TOP
USE
VFP tables 2nd
VFP
table commands 2nd
VFP syntax
versus VB .NET syntax 2nd 3rd
Visual FoxPro versus Visual Basic .NET
compiler directives 2nd
compiling
DLL
in parameterized VFP Web services 2nd
conditional execution
in VB .NET
If Expr Then...Else...End If statements
Select Case commands
Switch(VisualBasic Namespace) statements
in VFP 2nd
Config.Web files
Web services 2nd
configuring
AppScreen form properties 2nd
connected data access 2nd
connecting
to Internet servers 2nd 3rd
connection strings
connections
adding to forms 2nd
assigning providers to
creating
in Server Explorer
data sources 2nd
DBC
Console routine (Web Connection)
controls
ActiveX controls (Screen design) 2nd
adding to forms
VB .NET application creation 2nd
disabling
in VFP forms 2nd
enabling/disabling
in VB .NET forms 2nd
field controls (VFP Report Designer) 2nd
form controls
class libraries 2nd
color
enabling/disabling 2nd
Field Mapping
form creation
VFP application creation 2nd
graphical style controls
color 2nd
home-built controls (Screen design) 2nd 3rd 4th
label controls (VFP Report Designer)
line controls (VFP Report Designer)
MainMenu
MainMenu (VB .NET) 2nd
OLE bound controls (VFP Report Designer)
rectangle controls (VFP Report Designer)
screen controls
subclassing in VB .NET 2nd 3rd 4th
subclassing in VFP 2nd 3rd 4th 5th 6th
self-populating controls
creating 2nd 3rd
converting
VFP search form data
to SQL server 2nd 3rd 4th 5th 6th
VFP tables
to XML
XDR schemas to XSD schemas 2nd
copying
SQL tables to DBF 2nd
CREATE CURSOR commands
migrating VFP search form data
to SQL server
Creating a Distributed Application walkthrough
Cross-Tab Expert (Crystal Reports) 2nd
Crystal Reports 2nd
adding to other servers
in VS .NET Server Explorer
ASP .NET report clients 2nd
exporting as PDF files 2nd
building parameterized reports
in VS .NET Server Explorer 2nd 3rd 4th 5th 6th 7th
building reports
adding login information
adding user options 2nd
grouping fields
selecting fields
building XML Report Web Services 2nd
CrystalReportViewer
XML Report Web Services
CustomReportViewers
datasets as data sources
in VS .NET Server Explorer 2nd 3rd
displaying in VS .NET Server Explorer
Field Explorer
showing/hiding
filtering reports
in VS .NET Server Explorer
previewing reports 2nd 3rd
Report Experts 2nd
Cross-Tab Expert 2nd
Drill Down Expert 2nd 3rd 4th
Form Letter Expert 2nd 3rd
Standard Report Expert dialog
thin client applications
CrystalReportViewer (Crystal Reports)
XML Report Web Services
current directories
displaying
in Visual FoxPro 2nd
CursorAdapter Builder
Add Cursors page 2nd
commands
inserting 2nd 3rd 4th 5th
Data Access page
updates 2nd 3rd 4th 5th
CursorAdapter class 2nd 3rd 4th 5th
finding
manually coding 2nd 3rd
methods
CursorAttach
CursorDetach 2nd
CursorFill
properties
Alias
AllowDelete
AllowInsert
AllowUpdate
BufferModeOverride
CursorSchema 2nd
CursorStatus
DataSource
DataSourceType
FetchMemo
KeyFieldList
MaxRecords
SelectCmd
SendUpdates
Tables
UpdatableFieldList
UpdateGram
UpdateNameList
UpdateType
WhereType
CursorAdapters
creating 2nd 3rd 4th
reading XML 2nd 3rd 4th
CursorAttach method (CursorAdapter class)
CursorDetach method (CursorAdapter class) 2nd
CursorFill method (CursorAdapter class)
cursors 2nd 3rd
creating
XMLAdapter class 2nd
field names
updating
XMLAdapter class 2nd
XML in VFP
CursorSchema property (CursorAdapter class) 2nd
CursorSetProp functions
CursorStatus property (CursorAdapter class)
CursorToXML functions
CursorToXML() function
document validation 2nd
flags parameter
values of 2nd
moving data between tables and XML 2nd 3rd 4th
custom controls (Screen design)
date range pickers 2nd 3rd 4th
custom grids (screen design) 2nd
color-coded grids
incremental search grids 2nd
custom menus (Screen design) 2nd 3rd 4th 5th 6th
Customer forms (Internet application creation) 2nd 3rd
Customer tables
adding records to 2nd
VB .NET simple application creation
index creation
null fields
VFP simple application creation 2nd
customizing
IDE 2nd
report columns
in VFP Report Designer
solutions 2nd
CustomReportViewers (Crystal Reports)
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
data access
connected 2nd
disconnected 2nd
VB .NET
adding records to forms
data adapters 2nd 3rd 4th 5th 6th 7th 8th 9th
data adapters, dataset creation 2nd
data binding 2nd 3rd 4th 5th 6th 7th 8th
Data Form Wizard 2nd 3rd 4th 5th 6th 7th
data source connections 2nd 3rd 4th 5th
datasets 2nd 3rd 4th 5th 6th 7th
deleting records in forms
disconnected recordsets
loading datasets to forms 2nd
retrieving datasets from forms
skipping records in forms
typed datasets 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
15th 16th 17th
versus VFP 2nd 3rd
viewing generated code in forms 2nd
XML Web services 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
VFP 2nd
buffering tables 2nd 3rd 4th 5th 6th
cursors
DBF files
index creation
table aliases 2nd
table commands/functions 2nd
table creation
using tables 2nd
versus VB .NET 2nd 3rd
data access classes
events 2nd 3rd
forms 2nd 3rd 4th 5th
Data Access page (CursorAdapater Builder)
Data Adapter Wizard
data adapters
code generation 2nd 3rd
dataset creation 2nd
VFB tables 2nd 3rd 4th 5th
data binding
datasets
in VB .NET 2nd 3rd
in VB .NET 2nd 3rd 4th 5th
BindingContext references
in VFP
manual data binding
in VB .NET 2nd
tables 2nd 3rd 4th 5th
typed datasets
VB .NET 2nd 3rd
columns
rows 2nd
tables 2nd
Visual FoxPro versus Visual Basic .NET
Data Environment
CursorAdapter class 2nd
Data Form Wizard
forms
viewing generated code 2nd
setting options 2nd
Startup forms 2nd
Data Grouping dialog box (VFP Report Designer)
data islands
reducing server loads 2nd
Data Link Properties dialog (Server Explorer)
Provider page 2nd
data migration
SQL servers
data source tables
hooking keystrokes 2nd
loading grids 2nd
updating
data sources
connections 2nd 3rd 4th
assigning providers to
ODBC
connections
SQL Servers as 2nd
OLEDB
connections
data tables 2nd 3rd 4th
data tier class libraries
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
DeleteRecord routines
FillCursor routines 2nd
GetOneRecord routines 2nd
InsertRecord routines
SaveRecord routines
UpdateRecord routines
Data Tier programs
internet data access, adding to 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
12th
data tiers
form templates
data types
declaring 2nd 3rd
DataAdapter
building
DataAdapter Configuration Wizard 2nd
database applications
records
adding
deleting
modifying
XML files
database containers
databases
SQL databases
creating 2nd 3rd 4th 5th
DataEnvironment Builder
CursorAdapters
creating 2nd 3rd 4th
datasets
adding to XML Web services 2nd
as data sources in Crystal Reports 2nd 3rd
creating 2nd
VB .NET simple application creation 2nd
data binding 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
in VB .NET 2nd 3rd
loading to forms 2nd
reading XML into 2nd 3rd
retrieving from forms
typed datasets 2nd
data binding 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th
viewing 2nd
versus schemas
versus XML
Web References
DataSource property (CursorAdapter class)
DataSourceType property (CursorAdapter class)
DataTier code
VFP application creation 2nd 3rd 4th 5th
DeleteRecord routines
FillCursor routines 2nd
GetOneRecord routines 2nd
InsertRecord routines
SaveRecord routines
UpdateRecord routines
DataView 2nd 3rd
date range pickers (Screen design) 2nd 3rd 4th
date time fields
formatting
in VB .NET 2nd
DBC (database containers) 2nd
connections
handles
local views
remote views
SPT 2nd 3rd
DBF
copying SQL tables to 2nd
Visual FoxPro versus Visual Basic .NET 2nd 3rd
dbf files
DBF files 2nd 3rd
attaching indexes to 2nd
data access
DBC 2nd
transactions
DBFNextCustomer functions
app server creation
DBFs
loading to SQL servers 2nd 3rd 4th
debuggers 2nd 3rd
declaring
data types 2nd 3rd
events
in classes 2nd
FRIEND variables
in VB .NET
function scope
Public option
Shadows option
Shared option
Static option
functions
in VB .NET 2nd
in VFP 2nd
interfaces 2nd 3rd
LOCAL variables 2nd 3rd 4th
namespaces (VB .NET classes) 2nd 3rd
PRIVATE variables 2nd 3rd 4th
in VB .NET 2nd
procedures
in VFP 2nd
property procedures 2nd
PROTECTED FRIEND variables
in VB .NET
PROTECTED variables
in VB .NET
PUBLIC variables 2nd 3rd 4th
in VB .NET
READONLY variables
in VB .NET 2nd
SHADOWS variables
in VB .NET
SHARED variables
in VB .NET
STATIC variables
in VB .NET
subroutines
in VFP 2nd
TYPE variables
in VB .NET
variable scope 2nd 3rd 4th
variables 2nd 3rd 4th
WITHEVENTS variables
in VB .NET
default report layout
in VFP Report Designer 2nd
DefaultDataView 2nd 3rd
Delete buttons
Click event codes
creating in forms 2nd 3rd
DELETE commands
DeleteOneCustomer method
VB .NET Web services, building 2nd
DeleteRecord routines
data tier class library creation
deleting
menus
from VFP
records
from database application
records in forms
designing
forms
VB .NET simple application creation 2nd
menus
in VFP 2nd 3rd 4th 5th
startup screens
in VB .NET 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
16th 17th 18th 19th 20th 21st 22nd 23rd
in VFP 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th
31st 32nd 33rd 34th 35th 36th 37th 38th 39th 40th
dialog boxes
Data Grouping dialog box (VFP Report Designer)
Expression Builder dialog box (VFP Report Designer) 2nd
Title/Summary bands dialog box (VFP Report Designer) 2nd
dialogs
Standard Report Expert (Crystal Reports)
Tools, Options dialog
Visual FoxPro versus Visual Basic .NET 2nd 3rd 4th
diffgrams
creating
XMLAdapter class 2nd
receiving/processing
XMLAdapter class
DiffGrams
updating Web Server records 2nd 3rd
diffgrams
Web services
DIM statements
disabling
form controls
in VB .NET 2nd
in VFP 2nd
VSP application creation 2nd
disconnected data access 2nd
disconnected recordsets
displaying
Crystal Reports
in VS .NET Server Explorer
current directories
in Visual FoxPro 2nd
forms
project files
in Solution Explorer (Visual Basic .NET) 2nd
properties 2nd
records in tables
VB .Net application creation 2nd
DLL
compilation in parameterized Web services 2nd
DLL files
Solution Explorer (Visual Basic .NET)
Visual FoxPro versus Visual Basic .NET 2nd 3rd 4th
DLL registration (Web services)
DO UNTIL loops
DO WHILE loops 2nd 3rd
DO...ENDDO loops 2nd
DO...LOOP UNTIL loops
DO...LOOP WHILE loops
document validation
XMLDOM 2nd
Document View window
Visual FoxPro versus Visual Basic .NET
DOM (Document Object Model)
building XML code 2nd
calling Web services
document validation 2nd
properties
viewing
trapping XML errors
Drill Down Expert (Crystal Reports) 2nd 3rd 4th
DTD (Document Type Definition) schemas
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
EasySearch class
VB .NET search forms
VB .NETE search forms 2nd 3rd 4th 5th
VFP search forms
EasySearch templates
class code 2nd 3rd 4th 5th
Edit buttons
creating in forms 2nd 3rd
EditCustomer forms (Internet applications)
parameters, passing
property procedures 2nd 3rd
RecordKey property procedure
editing
subclassed screen control classes 2nd
element tags (XML)
elements (XML)
attributes
namespaces 2nd
enabling/disabling
form controls
in VB .NET 2nd
VSP application creation 2nd
encoding
in XML 2nd
ending
programs
Enterprise Manager
SQL database creation
userIDs
enumerations
IntelliSense 2nd
error handling
debuggers 2nd 3rd
error trapping
TRY...CATCH blocks 2nd
error trapping
errors
XML errors
trapping via DOM
event arguments
events 2nd 3rd 4th 5th 6th 7th
adding code to
BaseForm Load
VB .NET application creation 2nd
Click
Add buttons
Cancel buttons
Close buttons
Delete buttons
Save buttons
data access classes 2nd 3rd
forms 2nd 3rd 4th 5th
declaring in classes 2nd
event arguments
Init events
VFP reports 2nd 3rd
RaiseEvent 2nd
Unload event (VFP search forms)
Visual FoxPro versus Visual Basic .NET
EXE files
running
in Visual FoxPro
Visual FoxPro versus Visual Basic .NET 2nd
Expression Builder dialog box (VFP Report Designer) 2nd
expressions
calculated expressions
VFP reports 2nd 3rd
group expressions
VFP reports
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
FCOUNT() commands
FetchMemo property (CursorAdapter class)
FIELD commands
field controls (VFP Report Designer) 2nd
Field Explorer (Crystal Reports)
showing/hiding
Field Mapping
form controls
VFP application creation
field names
cursors
tables
fields
grouping
in Crystal Reports
Primary Key fields
VB .NET search forms 2nd
selecting
in Crystal Reports
files
.dll
Visual FoxPro creation
APP
running in Visual FoxPro
Visual FoxPro versus Visual Basic .NET 2nd
asmx files
XML Report Web Services 2nd
CDX
Config.Web files
Web services 2nd
DBF 2nd
data access
DBC 2nd
transactions
DBF files
attaching indexes to 2nd
displaying
in Solution Explorer (Visual Basic .NET) 2nd
DLL
Visual FoxPro versus Visual Basic .NET 2nd 3rd 4th
DLL files
Solution Explorer (Visual Basic .NET)
EXE
running in Visual FoxPro
Visual FoxPro versus Visual Basic .NET 2nd
frt files
frx files
FRX/FRT file pairs
HTML files
IDX files
MNX/MNT file pairs
MPR/MPX file pairs
PDF
publishing VFP reports to Internet
PDF files
exporting ASP .NET reporting clients 2nd
pjt files
pjx files
SCX/SCT file pairs
vb
VCX/VCT file pairs
w.connect.h files (Web Connection
WSDL (Web Services Discovery Language) files
WSDL files
building
XML
internet applications
XML files
Solution Explorer (Visual Basic .NET) 2nd
XML text files
FillCursor routines
data tier class library creation 2nd
filtering
records
in VB .NET 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th
in VFP 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
reports
in Crystal Reports
in SQL server 2nd 3rd
in VFP Report Designer 2nd 3rd
Find function
finding
CursorAdapter class
Flags parameter (CursorToXML() function)
values of 2nd
Flags parameter (XMLToCursor() function)
values of 2nd
FlatFileForm templates 2nd
class code 2nd 3rd 4th 5th
flow of control
VFP versus VB .NET
FOR...ENDDO loops
FOR...NEXT loops 2nd
form controls
class libraries
VFP application creation 2nd
enabling/disabling
VFP application creation 2nd
Field Mapping
VFP application creation
Form Designer
Form Letter Expert (Crystal Reports) 2nd 3rd
form loading buttons (Screen design)
formatting
date time fields
in VB .NET 2nd
forms
adding buttons to
VB .Net applications 2nd
adding command buttons 2nd 3rd
Add button 2nd 3rd 4th
Browse button 2nd 3rd
Cancel button 2nd
Delete button 2nd 3rd
Edit button 2nd 3rd
Next button 2nd 3rd
Previous button 2nd 3rd
Save button 2nd 3rd
adding connections to 2nd
adding controls to
VB .NET application creation 2nd
adding grids to
adding records 2nd
adding searches to
VB .NET apllications 2nd
AppScreen
property configuration 2nd
building
in VB .NET applications
changing code to class code 2nd
classes
VFP versus VB .NET
closing 2nd
coding classes
VB .NET applications 2nd
controls
color 2nd
creating
in VFP 2nd
VB .NET simple application creation
VFP application creation 2nd
data access class events 2nd 3rd 4th 5th
data binding 2nd 3rd 4th
Data Environment
CursorAdapter class 2nd
date time fields
formatting in VB .NET 2nd
deleting records 2nd
designing
VB .NET simple application creation 2nd
disabling controls
in VFP 2nd
displaying
enabling/disabling controls
in VB .NET 2nd
inheritable forms
creating 2nd
naming
selecting inherited classes for 2nd
inherited
loading datasets to 2nd
methods
VB .NET simple application creation 2nd
naming buttons
positioning buttons
resizing
retrieving datasets from
running 2nd 3rd
saving as classes 2nd 3rd
SDI forms (screen design) 2nd
skipping records 2nd
Startup forms
setting up 2nd
tab ordering 2nd
template creation 2nd
templates 2nd 3rd
data tiers
FlatFileForm 2nd 3rd 4th 5th 6th 7th
search form 2nd 3rd 4th 5th 6th 7th
user-settable options (screen design) 2nd 3rd
VB .NET search forms
creating 2nd 3rd
viewing generated code 2nd
XML Web services
testing 2nd 3rd 4th
FoxPro Grid (Screen design)
FoxPro Menu
menu design 2nd
fpt files
Friend option (functions/subroutines) 2nd
FRIEND variables
declaring
in VB .NET
frmEditCustomer form class
frt files
frx files
FRX/FRT file pairs
function signatures
functions 2nd 3rd
calling
CursorSetProp
CursorToXML
CursorToXML()
document validation 2nd
flags parameter values 2nd
moving data between tables and XML 2nd 3rd 4th
declaring
in VB .NET 2nd
in VFP 2nd
Friend option 2nd
GetCursorAdapter function
Implements clauses 2nd
interfaces
declaring 2nd 3rd
implementing in classes
methods
adding to PRG
adding to VCX
MustOverride option 2nd
NotOverridable option
Overloads option
Overridable option
Overrides option
Private option
Protected Friend option
Protected option
Public option
scope declarations
Public option
Shadows option
Shared option
Static option
SEND2DBF 2nd 3rd 4th
SEND2SQL 2nd 3rd 4th
Shadows option
Shared option
TableRevert() 2nd
Tablerevert()
data tier class libraries
TableUpdate() 2nd 3rd
data tier class libraries
VFP
table functions 2nd
Visual FoxPro versus Visual Basic .NET
XMLToCursor
XMLToCursor()
flags parameter values 2nd
moving data between tables and XML 2nd 3rd
XMLUpdateGram() 2nd 3rd 4th
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
GenRepoX
Get methods
GET parameters (Web Connection server functions) 2nd
GetAllCustomers method
GetCursorAdapter function
GetOneCustomer method
VB .NET Web services, building
GetOneRecord routines
data tier class library creation 2nd
getters
versus ASSIGN methods 2nd
GO BOTTOM commands
GO TOP commands
graphical style controls
color 2nd
grids
adding to forms
group expressions
VFP reports
grouping
fields in reports
in Crystal Reports
projects
Project Manager (Visual FoxPro) 2nd
report variables
in VFP Report Designer 2nd
groupings
adding to reports
in VFP Report Designer
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
handles
returning in SQL
handles (SQL commands)
opening/closing
Handles clauses
events
HIDDEN methods
hiding
Field Explorer (Crystal Reports)
hierarchies
representing in XML 2nd 3rd 4th 5th 6th
XML hierarchies
viewing in Visual Studio
home-built controls (Screen design) 2nd 3rd 4th
home-built grids (Screen design) 2nd
home-built menus (Screen design) 2nd 3rd 4th 5th 6th
hooking keystrokes
to data source tables 2nd
hotkeys
VFP menus
HTML files
HTTP
accessing SQL Server
data source settings
general settings
security settings
hyperlinks
Crystal Reports
HyperSnap tools (Screen design)
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
key fields
as user-specified strings
keyboard shortcuts
Visual FoxPro versus Visual Basic .NET 2nd
KeyFieldList property (CursorAdapter class)
keystrokes
hooking
to data source tables 2nd
keywords
NOCONSOLE
printing VFP report forms
PREVIEW
printing VFP report forms 2nd
SUMMARY
printing VFP report forms
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
MainMenu controls
MainMenu controls (VB .NET) 2nd
manual data binding
in VB .NET 2nd
manually coding
CursorAdapte class 2nd
CursorAdapter class
MaxRecords property (CursorAdapter class)
MDI (Mulitple Document Interfaces)
Menu Designer (VFP) 2nd 3rd
menus
building
in VB .NET applications 2nd
home-built menus (Screen design) 2nd 3rd 4th 5th 6th
hotkeys
JavaScript (Screen design) 2nd 3rd 4th 5th 6th
positioning
in VFP
VB .NET creation 2nd
VFP creation 2nd 3rd
Merge method
messaging
methods
ACCESS methods
versus setters 2nd
adding to PRG
adding to VCX
AddTableSchema (XMLAdapter class)
ASSIGN methods
versus getters 2nd
Attach (XMLAdapter class)
calling
CursorAttach (CursorAdapter class)
CursorDetach (CursorAdapter class) 2nd
CursorFill (CursorAdapter class)
getters
versus ASSIGN methods 2nd
HIDDEN
LoadXML (XMLAdapter class)
Property Set 2nd
PROTECTED
PUBLIC
setters
versus ACCESS methods 2nd
ToXML (XMLAdapter class)
VB .NET form methods 2nd
migrating
VFP search form data
to SQL server 2nd 3rd 4th 5th 6th 7th 8th 9th
missing values
MNX/MNT file pairs
modifying
database application records
moving
XML data to tables
CursorToXML() functions 2nd 3rd 4th
XMLToCursor() functions 2nd 3rd
MPR/MPX file pairs
MustOverride option (functions/subroutines) 2nd
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
Object Browser
Visual FoxPro versus Visual Basic .NET
objects
creating from class definitions
ODBC data sources
connections
SQL Servers as 2nd
OLE bound controls (VFP Report Designer)
OLEDB data sources
connections
Online Resources panel
Visual FoxPro versus Visual Basic .NET
opening
Properties sheet
Properties window
Server Explorer 2nd
Server Explorer (VS .NET)
Solution Explorer 2nd
Toolbox
opening/closing
handles
OrderBy property (VFP search forms)
Output window
Visual FoxPro versus Visual Basic .NET
Overloads option (functions/subroutines)
Overridable option (functions/subroutines)
Overrides option (functions/subroutines)
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
parameterized reports
adding parameters to
in Crystal Reports 2nd
adding selection formulas to
in Crystal Reports 2nd
building in Crystal Reports 2nd 3rd
parameterized Web services
VB .NET creation 2nd 3rd
VFP creation 2nd
DLL compilation 2nd
smart client applications 2nd
parameters
Flags parameter (CursorToXML() function)
values of 2nd
Flags parameter (XMLToCursor() function)
values of 2nd
GET (Web Connection server functions) 2nd
passing
in EditCustomer forms
POST (Web Connection server functions)
Web servers, sending to
via post buffer 2nd
passing
parameters
in EditCustomer forms
passwords
Enterprise Manager
PDF files
exporting ASP .NET reporting clients 2nd
VFP reports
publishing to Internet
phantom records (VPF table buffering) 2nd
pjt files
pjx files
positioning
buttons
menus
in VFP
post buffer
parameters
sending to Web servers 2nd
post buffers
variables, adding to
POST parameters (Web Connection server functions)
PREVIEW keyword
printing VFP report forms 2nd
previewing
reports
in Crystal Reports 2nd 3rd
Previous buttons
creating in forms 2nd 3rd
PRG
adding methods to
building
classes 2nd
Web Connections
Primary Key fields
VB .NET search forms 2nd
primary keys
integer keys as
VFP application creation
printers
specifying
in VFP Report Designer
switching
in VFP Report Designer
printing
reports
in VFP Report Designer
Private option (functions/subroutines)
PRIVATE variables
declaring 2nd 3rd 4th
in VB .NET 2nd
PROCEDURE libraries 2nd
procedures 2nd
calling
declaring
in VFP 2nd
methods
adding to PRG
adding to VCX
procedures (properties)
declaring 2nd
process class libraries
processing
diffgrams
programs
ending
project folders
solutions
Project Manager (Visual FoxPro)
projects
grouping 2nd
projects
grouping
Project Manager (Visual FoxPro) 2nd
properties 2nd
ACCESS methods
versus setters 2nd
Alias (CursorAdapter class)
AllowDelete (CursorAdapter class)
AllowInsert (CursorAdapter class)
AllowUpdate (CursorAdapter class)
AppScreen forms 2nd
ASSIGN methods
versus getters 2nd
BufferModeOverride (CursorAdapter class)
ColNames (VFP search forms)
ColWidths (VFP search forms)
command button properties 2nd
CursorSchema (CursorAdapter class) 2nd
CursorStatus (CursorAdapter class)
DataSource (CursorAdapter class)
DataSourceType (CursorAdapter class)
declaring variables
displaying 2nd
DOM properties
viewing
FetchMemo (CursorAdapter class)
getters
versus ASSIGN methods 2nd
KeyFieldList (CursorAdapter class)
MaxRecords (CursorAdapter class)
OrderBy (VFP search forms)
procedures
declaring 2nd
property procedures 2nd
Property Set method 2nd
ReportDocument (Crystal Reports) 2nd
ReportSource (Crystal Reports)
ReportViewer (Crystal Reports)
SelectCmd (CursorAdapter class)
SendUpdates (CursorAdapter class)
setters
versus ACCESS methods 2nd
solutions properties 2nd
subclasses 2nd
TableName (VFP search forms)
Tables (CursorAdapter class)
UpdatableFieldList (CursorAdapter class)
UpdateGram (CursorAdapter class)
UpdateNameList (CursorAdapter class)
UpdateType (CursorAdapter class)
Visual FoxPro versus Visual Basic .NET 2nd 3rd
WhereType (CursorAdapter class)
Properties sheet
opening
Properties window
opening
Visual FoxPro versus Visual Basic .NET 2nd 3rd
property procedures 2nd
creating
Property Set methods 2nd
Protected Friend option (functions/subroutines)
PROTECTED FRIEND variables
declaring
in VB .NET
PROTECTED methods
Protected option (functions/subroutines)
PROTECTED variables
declaring
in VB .NET
protocols
SOAP
Provider page (Server Explorer) 2nd
providers
assigning to connections
PUBLIC methods
Public option
function scope declarations
Public option (functions/subroutines)
PUBLIC variables
declaring 2nd 3rd 4th
in VB .NET
publishing
reports to Internet
via PDF files
via VFP Report Designer 2nd 3rd 4th
Web services
[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N]
[O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]
Quicken interface (Screen design) 2nd 3rd 4th 5th 6th 7th
quitting
programs
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
RaiseEvent 2nd
reading
XML
via CursorAdapters 2nd 3rd 4th
XML into datasets 2nd 3rd
READONLY variables
declaring
in VB .NET 2nd
RECCOUNT() commands
receiving
diffgrams
RECNO() commands
record filtering
in VB .NET 2nd 3rd
DefaultDataView 2nd 3rd
minimalist filtering 2nd
returning values in search forms 2nd
search forms 2nd 3rd
in VFP 2nd
column sorting in record lookup grids 2nd 3rd 4th 5th
creating search forms 2nd 3rd 4th 5th 6th
minimalist filtering 2nd
record lookup grids (VFP)
column sorting 2nd 3rd 4th 5th
RecordKey property procedures (EditCustomer forms)
records
adding to forms
adding to SQL
adding to tables
VB .NET table creation
VFP table creation
deleting in forms 2nd
displaying in tables
VB .NET application creation 2nd
RecordSets 2nd
rectangle controls (VFP Report Designer)
references
BindingContext
data binding in VB .NET
registering
DLL (Web services)
remote views
DBC
remoting
removing
menus
from VFP
Report Designer (VFP)
Data Grouping dialog box
default report layout 2nd
Expression Builder dialog box 2nd
opening
printing reports 2nd
reports
adding groupings to
calculated expressions 2nd 3rd
customizing columns
example of 2nd
field controls 2nd
filtering 2nd 3rd
grouping with report variables 2nd
label controls
landscape reports
line controls
OLE bound controls
printing
publishing to Internet 2nd 3rd 4th 5th
rectangle controls
specifying printers
switching printers
Title/Summary bands 2nd
variables 2nd
Report Experts (Crystal Reports) 2nd
Cross-Tab Expert 2nd
Drill Down Expert 2nd 3rd 4th
Form Letter Expert 2nd 3rd
report variables
grouping with
in VFP Report Designer 2nd
ReportDocument property (Crystal Reports) 2nd
reports
adding groupings to
in VFP Report Designer
building
in Crystal Reports 2nd 3rd 4th
building parameterized reports
in Crystal Reports 2nd 3rd 4th 5th 6th 7th
columns
customizing
default layout
in VFP Report Designer 2nd
example of
in VFP Report Designer 2nd
field controls (VFP Report Designer) 2nd
filtering
in Crystal Reports
in SQL server 2nd 3rd
in VFP Report Designer 2nd 3rd
GenRepoX
grouping fields
in Crystal Reports
label controls (VFP Report Designer)
landscape reports
line controls (VFP Report Designer)
OLE bound controls (VFP Report Designer)
previewing
in Crystal Reports 2nd 3rd
printers
specifying
switching
printing
in VFP Report Designer
publishing to Internet
via PDF files
via VFP Report Designer 2nd 3rd 4th
rectangle controls (VFP Report Designer)
selecting fields
in Crystal Reports
storing
in VFP
VFP reports
calculated expressions 2nd 3rd
group expressions
Init events 2nd 3rd
variables 2nd
ReportSource property (Cyrstal Reports)
ReportViewer property (Cyrstal Reports)
Request objects
Internet applications, building
reserved words (SQL)
resizing
forms
Response objects
Internet applications, building
retreiving
datasets from forms
returning
handles in SQL
values
in VB .NET search forms 2nd
routines
DeleteRecord
data tier class library creation
FillCursor
data tier class library creation 2nd
GetOneRecord
data tier class library creation 2nd
InsertRecord
data tier class library creation
LoadList_Click
SaveRecord
data tier class library creation
subroutines
Inputs 2nd
UpdateRecord
data tier class library creation
rows
data binding 2nd
running
forms 2nd 3rd
VFP appliciations 2nd
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
Save buttons
Click event codes
creating in forms 2nd 3rd
SaveRecord routines
data tier class library creation
saving
buttons as classes 2nd
forms as classes 2nd 3rd
menu designs
in VFP
SCAN ...ENDSCAN commands
schemas
DTD schemas
versus datasets
XDR
XML 2nd
XML schemas
creating via Visual Studio XML editors 2nd
DTD
XSD
XML 2nd 3rd 4th
Screen (background)
Visual FoxPro versus Visual Basic .NET
screen controls
subclassing
editing classes 2nd
in VB .NET 2nd 3rd 4th
in VFP 2nd 3rd 4th
screen design
VB .NET
ActiveX controls 2nd
home-built menus 2nd 3rd 4th
HyperSnap tools
IconCool tools
Quicken interface 2nd 3rd 4th
VFP
color 2nd 3rd
color-coded grids
form control color 2nd
form loading buttons
FoxPro Grid
graphical style control color 2nd
home-built grids 2nd
home-built menus 2nd 3rd 4th 5th 6th
incremental search grids 2nd
list boxes 2nd
Quicken interface 2nd 3rd
SDI forms 2nd
tree views
user-settable options 2nd 3rd
Windows Design Guide
SCX/SCT file pairs
SDI (Single Document Interfaces)
SDI forms (Screen design) 2nd
Search and Replace screen (VFP search forms)
search form templates
EasySearch templates
class code 2nd 3rd 4th 5th
search forms
creating in VB .NET 2nd 3rd
VB .NET creation
EasySearch class 2nd 3rd 4th 5th 6th
Primary Key fields 2nd
self-populating controls 2nd 3rd
VB .NET search forms
returning values in 2nd
VFP
Audit screen 2nd
cleaning up data 2nd 3rd 4th 5th
migrating data to SQL server 2nd 3rd 4th 5th 6th 7th 8th 9th
Search and Replace screen
VFP creation 2nd 3rd 4th 5th 6th
Clear button 2nd
ColNames property
ColWidths property
EasySearch class
OrderBy property
Select button
Show Matches button 2nd
TableName property
Unload event
searches
adding to forms
VB .NET applications 2nd
security
passwords
Enterprise Manager
userIDs
Enterprise Manager
Select button (VFP search forms)
Select Case commands
SELECT command
VFP tables
SELECT statements
Internet applications, building
SelectCmd property (CursorAdapter class)
selecting
fields in reports
in Crystal Reports
inheritable classes
for inheritable forms 2nd
project language (VB .NET application creation)
project type (VB .NET application creation)
selection formulas
adding to parameterized reports
in Crystal Reports 2nd
self-populating controls
creating 2nd 3rd
SEND2DBF functions 2nd 3rd 4th
SEND2SQL functions 2nd 3rd 4th
sending
paramters
to Web servers via post buffers 2nd
SendUpdates property (CursorAdapter class)
Server Explorer
connection creation
Data Link Properties dialog
Provider page 2nd
opening 2nd
Visual FoxPro versus Visual Basic .NET 2nd 3rd
Server Explorer (VS .NET)
adding Crystal Reports to other servers
Crystal Reports
building parameterized reports 2nd 3rd 4th 5th 6th 7th
datasets as data sources 2nd 3rd
filtering reports
displaying Crystal Reports
opening
server functions (Web Connection)
writing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
GET parameters 2nd
POST parameters
servers
app servers
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
Internet servers
building 2nd
connecting to 2nd 3rd
reducing loads
via data islands 2nd
via XML
SQL 2nd 3rd
data migration
IDENTITY feature 2nd
loading DBFs to 2nd 3rd 4th
missing values
SQL server
filtering reports 2nd 3rd
SET FIELDS command (VFP)
record filtering
SET FILTER command (VFP)
record filtering 2nd
Set methods
setters
versus ACCESS methods 2nd
Shadows option
function scope declarations
Shadows option (functions/subroutines)
SHADOWS variables
declaring
in VB .NET
Shared option
function scope declarations
Shared option (functions/subroutines)
SHARED variables
declaring
in VB .NET
shortcut keys
Visual FoxPro versus Visual Basic .NET 2nd
Show Matches button (VFP search forms) 2nd
simple applications
VB .NET creation
adding command buttons to forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
11th
Customer table creation 2nd 3rd
dataset creation 2nd
form creation
form design 2nd
form methods 2nd
VFP creation
adding command buttons to forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
11th 12th 13th
Customer table creation 2nd
default setting changes
enabling/disabling form controls 2nd
Field Mapping form controls
form controls class libraries 2nd
form creation 2nd
form template creation 2nd 3rd 4th 5th 6th
running forms 2nd
SKIP commands
skipping
records in forms
smart client applications
parameterized VFP Web services 2nd
remoting
snippets (code)
accessing
in Virtual Basic .NET
SOAP (Simple Object Access Protocol)
Solution Explorer
Class View window
viewing typed datasets 2nd
dataset creation 2nd
opening 2nd
typed datasets 2nd
viewing 2nd
Solution Explorer (Visual Basic .NET)
DLL files
intermediate language
projects
displaying files 2nd
typed datasets
XML files 2nd
solutions
customizing 2nd
Visual FoxPro versus Visual Basic .NET 2nd 3rd
sorting
columns
in record lookup grids (VFP) 2nd 3rd 4th 5th
specifying
printers
in VFP Report Designer
SPT (SQL Pass-Through) 2nd 3rd
SQL
adding records to
backups
commands
handles
SQLCANCEL()
SQLCOLUMNS()
SQLCOMMIT()
SQLConnect()
SQLDisconnect()
SQLExec()
SQLGETPROP()
SQLMORERESULTS()
SQLPREPARE()
SQLROLLBACK()
SQLSETPROP()
SQLStringConnect
SQLTABLES()
connections
copying tables to DBF 2nd
database creation 2nd 3rd 4th 5th
functions
SQLDMO.CHM
handles
indexes
INSERT statements
building 2nd 3rd 4th
NULLs
reserved words
returning handles
SQLDMO.DLL
transaction
UPDATE statements
building 2nd 3rd 4th
SQL Server
accessing
http data source settings
http general settings
http security settings
accessing via XML 2nd 3rd
IDENTITY feature 2nd
SQL server
migrating VFP search form data to 2nd 3rd 4th 5th 6th 7th 8th 9th
reports
filtering 2nd 3rd
SQL Server
tables
creating 2nd
SQL Servers 2nd 3rd
as ODBC data sources 2nd
data migration
loading DBFs to 2nd 3rd 4th
missing values
SQL Upsizing Wizard
SQLCANCEL() commands
SQLCOLUMNS() commands
SQLCOMMIT() commands
SQLConnect() commands
SQLDisconnect() commands
SQLDMO.CHM
SQLDMO.DLL (SQL Data Management Objects)
SQLExec() commands
SQLGETPROP() commands
SQLMORERESULTS() commands
SQLNextCustomer functions
app server creation
SQLPREPARE() commands
SQLROLLBACK() commands
SQLSETPROP() commands
SQLStringConnect commands
SQLTABLES() commands
Standard Report Expert dialog (Crystal Reports)
StandardForm class template
StandardForm Class templates 2nd 3rd
Startup forms
setting up 2nd
startup screens
VB .NET creation 2nd
ActiveX controls 2nd
data binding 2nd 3rd 4th 5th
example of 2nd 3rd 4th 5th
home-built controls 2nd 3rd 4th
HyperSnap tools
IconCool tools
Quicken interface 2nd 3rd 4th
VFP creation 2nd 3rd
color 2nd 3rd
color-coded grids
data binding
example of 2nd 3rd 4th
form control color 2nd
form loading buttons
FoxPro Grid
graphical style control color 2nd
home-built grids 2nd
home-built menus 2nd 3rd 4th 5th 6th
incremental search grids 2nd
list boxes 2nd
MDI
Quicken interface 2nd 3rd
SDI
SDI forms 2nd
tree views
user-settable options 2nd 3rd
Windows Design Guide
statements
INSERT statements (SQL)
building 2nd 3rd 4th
UPDATE statements (SQL)
building 2nd 3rd 4th
Static option
function scope declarations
STATIC variables
declaring
in VB .NET
storing
reports
in VFP
subclasses
properties 2nd
subclassing
screen controls
editing classes 2nd
in VB .NET 2nd 3rd 4th
in VFP 2nd 3rd 4th
subroutines 2nd
declaring
in VFP 2nd
Friend option 2nd
Inputs 2nd
MustOverride option 2nd
NotOverridable option
Overloads option
Overridable option
Overrides option
Private option
Protected Friend option
Protected option
Public option
Shadows option
Shared option
SUMMARY keyword
printing VFP report forms
Switch(VisualBasic Namespace) statements
switching
printers
in VFP Report Designer
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
tab ordering
forms 2nd
table fields 2nd
TableName property (VFP search forms)
TableRevert() functions 2nd
Tablerevert() functions
data tier class libraries
tables
aliases 2nd
buffering
phantom records 2nd
TableRevert() functions
TableUpdate() functions 2nd
creating
in SQL Server 2nd
in VBF
cursors
Customer tables
adding records to 2nd
VB .NET simple application creation 2nd 3rd
VFP simple application creation 2nd
data binding 2nd 3rd
columns
rows 2nd
data source tables
hooking keystrokes 2nd
loading grids 2nd
updating
date time fields
formatting in VB .NET 2nd
DBC 2nd
displaying records in
VB .NET application creation 2nd
field names
fields
listing in VFP
integer keys as primary keys
moving data to XML
CursorToXML() functions 2nd 3rd 4th
XMLToCursor() functions 2nd 3rd
null fields
SQL tables
copying to DBF 2nd
using
in VBF
in VFP
VFP
commands/functions 2nd
VFP application creation
testing data 2nd
VFP tables
converting to XML
VB .NET data adapters 2nd 3rd 4th 5th
Tables property (CursorAdapter class)
TableUpdate() functions 2nd 3rd
data tier class libraries
Task Lists
Visual FoxPro versus Visual Basic .NET
Task Pane Manager
Visual FoxPro versus Visual Basic .NET
templates
cmdAdd.click codes
creating in forms 2nd
data tiers
FlatFileForm 2nd
class code 2nd 3rd 4th 5th
form templates 2nd
search form
EasySearch templates 2nd 3rd 4th 5th 6th
StandardForm class templates
StandardForm Class templates 2nd 3rd
VFP form creations
testing
Table data
VFP application creation 2nd
Web services
in VFP 2nd
XML Web services 2nd
thin client applications 2nd
Crystal Reports
threads
messaging
three-tier data access
Title/Summary bands (VFP Report Designer) 2nd
Toolbox
opening
Visual FoxPro versus Visual Basic .NET 2nd 3rd
Tools, Options dialog
Visual FoxPro versus Visual Basic .NET 2nd 3rd 4th
TOP commands
ToXML method (XMLAdapter class)
transactions
transformation format notation (XML) 2nd
trapping
XML errors
via DOM
tree views (Screen design)
TRY...CATCH blocks 2nd
TYPE variables
declaring
in VB .NET
typed datasets 2nd 3rd 4th
data binding 2nd 3rd 4th 5th 6th 7th 8th 9th
columns
in VB .NET 2nd 3rd
rows 2nd
tables 2nd
viewing 2nd
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
variables
declaring 2nd 3rd 4th
DIM statements
FRIEND
declaring in VB .NET
LOCAL
declaring 2nd 3rd 4th
post buffers
adding variables to
PRIVATE
declaring 2nd 3rd 4th
declaring in VB .NET 2nd
PROTECTED
declaring in VB .NET
PROTECTED FRIEND
declaring in VB .NET
PUBLIC
declaring 2nd 3rd 4th
declaring in VB .NET
READONLY
declaring in VB .NET 2nd
report variables
grouping with in VFP Report Designer 2nd
scope
declaring 2nd 3rd 4th
SHADOWS
declaring in VB .NET
SHARED
declaring in VB .NET
STATIC
declaring in VB .NET
table fields 2nd
TYPE
declaring in VB .NET
VFP reports 2nd
VPN versus VB .NET
WITHEVENTS
declaring in VB .NET
VB .NET
aliases
application creation 2nd 3rd
adding buttons to forms 2nd
adding controls to forms 2nd
adding searches to forms 2nd
AppScreen form configuration 2nd
BaseForm Load events 2nd
button Click event codes 2nd 3rd 4th 5th
coding form classes 2nd
declaring property variables
displaying properties 2nd
displaying table records 2nd
form creation
inheritable form creation 2nd
Inputs subroutines 2nd
LoadList-Click routines
menu creation 2nd
project names
project type/language selection
property procedures 2nd
selecting inheritable classes for inheritable forms 2nd
using form templates 2nd
Windows Controls libraries 2nd 3rd
arrays 2nd
base control classes 2nd
classes 2nd
data access classes 2nd 3rd 4th 5th 6th 7th 8th
namespace declarations 2nd 3rd
namespaces 2nd
collections
commands
syntax 2nd 3rd
compiler directives 2nd
conditional execution
If Expr Then...Else...End If statements
Select Case commands
Switch(VisualBasic Namespace) statements
data access 2nd 3rd
adding records to forms
data adapters 2nd 3rd 4th 5th 6th 7th 8th 9th
data adapters, dataset creation 2nd
data binding 2nd 3rd 4th 5th 6th 7th 8th
Data Form Wizard 2nd 3rd 4th 5th 6th 7th
data source connections 2nd 3rd 4th 5th
datasets 2nd 3rd 4th 5th 6th 7th
deleting records in forms
disconnected recordsets
loading datasets to forms 2nd
retrieving datasets from forms
skipping records in forms
typed datasets 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
15th 16th 17th
viewing generated code in forms 2nd
XML Web services 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
Data binding 2nd 3rd 4th
data binding 2nd 3rd 4th 5th
BindingContext references
datasets 2nd 3rd
manual data binding 2nd
data source tables
hooking keystrokes to 2nd
data storage 2nd 3rd 4th 5th 6th
data tables 2nd 3rd 4th
data types
declaring 2nd
DataAdapter Configuration Wizard 2nd
datasets
data binding 2nd 3rd
reading XML into 2nd 3rd
date time fields
formatting 2nd
DefaultDataView 2nd 3rd
DIM statements
DO UNTIL loops
DO WHILE loops 2nd
DO...LOOP UNTIL loops
DO...LOOP WHILE loops
enumerations 2nd
error handling
debuggers 2nd 3rd
error trapping
TRY...CATCH blocks 2nd
events 2nd 3rd 4th
data access classes 2nd 3rd 4th 5th 6th 7th 8th
declaring in classes 2nd
event arguments
RaiseEvent 2nd
flow of control
FOR...NEXT loops
forms
data access class events 2nd 3rd 4th 5th
enabling/disabling controls 2nd
FRIEND variables
declaring
functions 2nd 3rd
calling
declaring 2nd
declaring interfaces 2nd 3rd
Friend option 2nd
implementing interfaces in classes
MustOverride option 2nd
NotOverridable option
Overloads option
Overridable option
Overrides option
Private option
Protected Friend option
Protected option
Public option
scope declarations 2nd 3rd 4th
Shadows option
Shared option
importing XML data
Internet applications
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
16th 17th 18th 19th 20th
MainMenu controls 2nd
menus
creating 2nd
methods
calling
parameterized Web service creation 2nd 3rd
PRIVATE variables
declaring 2nd
procedures
calling
programs
ending
project folders
solutions
property procedures 2nd
Property Set method 2nd
PROTECTED FRIEND variables
declaring
PROTECTED variables
declaring
PUBLIC variables
declaring
READONLY variables
declaring 2nd
record filtering 2nd 3rd
DefaultDataView 2nd 3rd
minimalist filtering 2nd
returning values in search forms 2nd
search forms 2nd 3rd
returning values
in search forms 2nd
search forms
creating 2nd 3rd
EasySearch class 2nd 3rd 4th 5th 6th
Primary Key fields 2nd
self-populating controls 2nd 3rd
Server Explorer
opening
SHADOWS variables
declaring
SHARED variables
declaring
simple application creation
adding command buttons to forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
11th
Customer table creation 2nd 3rd
dataset creation 2nd
form creation
form design 2nd
form methods 2nd
solutions
customizing 2nd
startup screens
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
16th 17th 18th 19th 20th 21st 22nd 23rd
STATIC variables
declaring
subclassing screen controls 2nd 3rd 4th
subroutines 2nd
Friend option 2nd
MustOverride option 2nd
NotOverridable option
Overloads option
Overridable option
Overrides option
Private option
Protected Friend option
Protected option
Public option
Shadows option
Shared option
TYPE variables
declaring
typed datasets
data binding
variables
declaring 2nd 3rd
vb files
Web services
building 2nd 3rd 4th 5th
Config.Web files 2nd
WHILE...WEND loops
WITHEVENTS variables
declaring
XML text files
vb files
VB.Net
class libraries
building 2nd
VBF
tables
creating
using
VCX
classes
VCX (Visual Class Library)
adding methods to
VCX/VCT file pairs
VFP
accessing SQL Server via XML 2nd 3rd
application creation
data tier class library creation 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
12th
form templates 2nd 3rd 4th 5th 6th 7th
primary keys 2nd
running applications 2nd
search form templates 2nd 3rd 4th 5th 6th 7th
SQL database creation 2nd 3rd 4th 5th
SQL Servers 2nd 3rd
testing table data 2nd
three-tier data access
writing applications 2nd 3rd 4th 5th
arrays 2nd
backups
base control classes 2nd
class libraries
building 2nd
classes
events
storing 2nd 3rd
collections
commands
SET FIELDS;record filtering
SET FILTER;record filtering 2nd
SQLCANCEL()
SQLCOLUMNS()
SQLCOMMIT()
SQLConnect()
SQLDisconnect()
SQLExec()
SQLGETPROP()
SQLMORERESULTS()
SQLPREPARE()
SQLROLLBACK()
SQLSETPROP()
SQLStringConnect()
SQLTABLES()
syntax 2nd 3rd
table commands 2nd
compiler directives 2nd
conditional execution 2nd
converting tables
to XML
CREATE CURSOR command
migrating search form data to SQL server
Crystal Reports 2nd
cursors
field names
data access 2nd 3rd 4th 5th
buffering tables 2nd 3rd 4th 5th 6th
cursors
DBF files
index creation
table aliases 2nd
table commands/functions 2nd
table creation
using tables 2nd
data binding 2nd 3rd 4th 5th
data source tables
hooking keystrokes to 2nd
data storage 2nd 3rd 4th
data tables 2nd 3rd 4th
data types
declaring 2nd 3rd
DBC 2nd
connections
handles
local views
remote views
SPT 2nd 3rd
DBF files
DBC 2nd
transactions
DO WHILE loops
Do...ENDDO loops 2nd
enumerations 2nd
error handling
debuggers 2nd 3rd
error trapping
TRY...CATCH blocks 2nd
events 2nd 3rd 4th
adding code to
flow of control
FOR...ENDDO loops
FOR...NEXT loops
forms
creating 2nd
disabling controls 2nd
displaying
FRX/FRT file pairs
functions 2nd 3rd
CursorToXML() 2nd 3rd 4th 5th 6th 7th 8th
declaring 2nd 3rd
table functions 2nd
TableRevert()
TableUpdate() 2nd
XMLToCursor() 2nd 3rd 4th 5th
HTML files
importing XML data 2nd
indexes
creating
Internet applications
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th
30th 31st 32nd 33rd 34th 35th 36th 37th 38th 39th 40th 41st 42nd 43rd
44th 45th 46th 47th 48th 49th
Web Connection 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th
29th 30th 31st 32nd 33rd 34th 35th 36th 37th 38th 39th 40th 41st 42nd
43rd 44th 45th 46th
Menu Designer 2nd 3rd
menus
creating 2nd 3rd
hotkeys
positioning
MNX/MNT file pairs
MPR/MPX file pairs
parameterized Web service creation 2nd
DLL compilation 2nd
smart client applications 2nd
pjt files
pjx files
procedures 2nd
declaring
declaring in VFP 2nd
reading foreign data via XML 2nd
record filtering 2nd
column sorting in record lookup grids 2nd 3rd 4th 5th
creating search forms 2nd 3rd 4th 5th 6th
minimalist filtering 2nd
Report Designer
adding groupings to reports
calculated expressions 2nd 3rd
customizing columns in reports
Data Grouping dialog box
default report layout 2nd
Expression Builder dialog box 2nd
field controls 2nd
filtering reports 2nd 3rd
grouping with report variables 2nd
label controls
landscape reports
line controls
OLE bound controls
opening
printing reports 2nd 3rd
publishing reports to Internet 2nd 3rd 4th 5th
rectangle controls
report example 2nd
specifying printers
switching printers
Title/Summary bands 2nd
variables 2nd
reports
calculated expressions 2nd 3rd
group expressions
Init events 2nd 3rd
storing
variables 2nd
SCX/SCT file pairs
search forms
Audit screen 2nd
cleaning up data 2nd 3rd 4th 5th
Clear button 2nd
ColNames property
ColWidths property
creating 2nd 3rd 4th 5th 6th
EasySearch class
migrating data to SQL server 2nd 3rd 4th 5th 6th 7th 8th 9th
OrderBy property
Search and Replace screen
Select button
Show Matches button 2nd
TableName property
Unload event
simple application creation
adding command buttons to forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
11th 12th 13th
Customer table creation 2nd
default setting changes
enabling/disabling form controls 2nd
Field Mapping form controls
form controls class libraries 2nd
form creation 2nd
form template creation 2nd 3rd 4th 5th 6th
running forms 2nd
startup screens
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th
30th 31st 32nd 33rd 34th 35th 36th 37th 38th 39th 40th 41st
subclassing screen controls 2nd 3rd 4th
editing classes 2nd
subroutines
declaring in VFP 2nd
table fields 2nd
tables
aliases 2nd
buffering 2nd 3rd 4th 5th 6th
commands/functions 2nd
DBC 2nd
listing fields
using
VB .NET data adapters 2nd 3rd 4th 5th
transactions
Upsizing Wizard 2nd
variable scope
declaring 2nd 3rd 4th
VCX/VCT file pairs
Web services
adding to Internet applications 2nd 3rd
building 2nd
building WSDL files
diffgrams
DLL registration
IIS virtual directories
publishing
testing 2nd
writing functions
XML
as cursors
XML implementation 2nd
VFP 8
CursorAdapter class 2nd 3rd 4th 5th
finding
manually coding 2nd 3rd
methods 2nd
properties 2nd 3rd 4th 5th
reading XML 2nd 3rd 4th
DiffGrams
updating Web Server records 2nd 3rd
GetCursorAdapter function
XMLAdapter class 2nd 3rd
AddTableSchema method
Attach method
converting VFP tables to XML
converting XDR schemas to XSD schemas 2nd
cursor creation 2nd
diffgram creation 2nd
LoadXML method
receiving/processing diffgrams
ToXML method
updating cursors 2nd
Web Service creation 2nd 3rd 4th
viewing
DOM properties
typed datasets 2nd
XML
in Visual Studio
XMl hierarchies
in Visual Studio
views
local
DBC
remote
DBC
Virtual Basic .NET
breakpoints
setting
Visual Basic .NET
IDE
customizing 2nd
Immediate mode
projects
running 2nd
property procedures
creating
Solution Explorer
displaying project files 2nd
DLL files
intermediate language
typed datasets
XML files 2nd
versus Visual FoxPro
APP files 2nd
Bookmarks
class browser
Class View
Class View window 2nd
code window 2nd
command windows 2nd 3rd
commands
data binding
DBF 2nd 3rd
DLL files 2nd 3rd 4th
Document View window
events
EXE files 2nd
functions
Object Browser
Online Resources panel
Output window
project development 2nd 3rd
properties 2nd 3rd
Properties window 2nd 3rd
Screen (background)
Server Explorer 2nd 3rd
shortcut keys 2nd
solutions 2nd 3rd
Task Lists
Task Pane Manager
Toolbox 2nd 3rd
Tools, Options dialog 2nd 3rd 4th
Visual FoxPro
.dll file creation
APP files
running
command window
executing prior commands
current directories
displaying 2nd
DLL files
EXE files
running
IDE
customizing 2nd
Project Manager
grouping projects 2nd
projects
running
versus Visual Basic .NET
APP files 2nd
Bookmarks
class browser
Class View
Class View window 2nd
code window 2nd
command windows 2nd 3rd
commands
data binding
DBF 2nd 3rd
DLL files 2nd 3rd 4th
Document View window
events
EXE files 2nd
functions
Object Browser
Online Resources panel
Output window
project development 2nd 3rd
properties 2nd 3rd
Properties window 2nd 3rd
Screen (background)
Server Explorer 2nd 3rd
shortcut keys 2nd
solutions 2nd 3rd
Task Lists
Task Pane Manager
Toolbox 2nd 3rd
Tools, Options dialog 2nd 3rd 4th
Visual Studio
XML
viewing
XML Editor
creating XML schemas 2nd
XML hierarchies
viewing
Visual Studio IDE
Server Explorer
connection creation
Data Link Properties dialog 2nd 3rd
opening
VPF
commands
APPEND BLANK 2nd 3rd
BOTTOM
DELETE
FCOUNT()
FIELD
GO BOTTOM
GO TOP
RECCOUNT()
RECNO()
SCAN...ENDSCAN
SKIP
TOP
functions
CursorSetProp
CursorToXML
TableRevert()
TableUpdate()
XMLToCursor
VPN
variables
VPR
functions
calling
methods
calling
procedures
calling
programs
ending
VS .NET
Crystal Reports
adding to other servers in Server Explorer
ASP .NET reporting clients 2nd 3rd 4th
building reports
building reports, adding login information
building reports, adding user options 2nd
building reports, grouping fields
building reports, selecting fields
building XML Report Web Services 2nd
displaying in Server Explorer
previewing reports 2nd 3rd
thin client applications
Server Explorer
adding Crystal Reports to other servers
building parameterized reports;in Crystal Reports 2nd 3rd 4th 5th 6th 7th
datasets as data sources in Crystal Reports 2nd 3rd
displaying Crystal Reports
filtering reports;in Crystal Reports
opening
[SYMBOL]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
XDR schemas
converting to XSD schemas 2nd
XML 2nd
XML
converting VFP tables to
data string conversion 2nd
records, example of
VB .NET data storage 2nd 3rd 4th 5th 6th
versus datasets
XMLTOCURSOR() methods 2nd
XML (Extensible Markup Language) 2nd 3rd 4th 5th
accessing SQL Server 2nd 3rd
as VFP cursors
BOM
CursorAdapter class 2nd 3rd
CursorAdapters 2nd 3rd 4th
DOM
building XML code in 2nd
calling Web services
document validation 2nd
trapping errors
viewing properties
element tags
elements
attributes
namespaces 2nd
encoding in 2nd
examples of 2nd
foreign data
reading in VFP 2nd
functions
XMLUpdateGram() 2nd 3rd 4th
hierarchies
representing 2nd 3rd 4th 5th 6th
viewing in Visual Studio
importing data into VB .NET
importing data into VFP 2nd
moving data to tables
CursorToXML() functions 2nd 3rd 4th
XMLToCursor() functions 2nd 3rd
namespaces 2nd
reading into datasets 2nd 3rd
RecordSets 2nd
reducing server loads
schemas
creating via Visual Studio XML Editor 2nd
DTD
structure of 2nd 3rd 4th 5th 6th
UpdateGrams
creating
deletes 2nd
updates
UTF-8 2nd
VFP implementation 2nd
viewing in Visual Studio
XDR schemas 2nd
XSD schemas 2nd
document validation 2nd
XML files
Internet applications
Solution Explorer (Visual Basic .NET) 2nd
XML Report Web Services
asmx files 2nd
building 2nd
CrystalReportViewer (Crystal Reports)
XML text files
XML Web services 2nd
adding datasets to 2nd
building
building WSDL files
creating 2nd 3rd
diffgrams
DLL registration
IIS virtual directories
Internet applications, adding to 2nd 3rd
publishing
testing 2nd 3rd 4th
updating
writing functions
XMLAdapter class 2nd 3rd
cursor creation 2nd
cursors
updating 2nd
diffgram creation 2nd
diffgrams
receiving/processing
methods
AddTableSchema
Attach
LoadXML
ToXML
VFP tables
converting to XML
Web Service creation 2nd 3rd 4th
XDR schemas
converting to XSD schemas 2nd
XMLAdapter classes
XMLDOM
document validation 2nd
XMLDOM (XML Document Object Model)
calling Web services
XMLToCursor functions
XMLToCursor() function
flags parameter
values of 2nd
moving data between tables and XML 2nd 3rd
XMLTOCURSOR() methods 2nd
XMLUpdateGram() functions 2nd 3rd 4th
XPATH 2nd
XSD schemas
converting XDR schemas to 2nd
XML 2nd
document validation 2nd
XSLT (Extensible Style Sheet Transformations) 2nd