My.ADVISOR.com Sign-In
ID
Password

Member Center / Sign-Up
Go to Article
Advanced Search 

DEVELOPMENT

Differences Between Visual FoxPro and Visual Basic .NET: Part 2

Are you beginning to migrate your development to Microsoft Visual Basic .NET from Visual FoxPro, or maybe just adding VB .NET to your list of skills? Before you start building applications, review the principal differences between the technologies.

By Les Pinter


This article is a continuation of Part 1, which you can read at http://Advisor.com/doc/14437.

Functions and Subroutines (Procedures)
This excerpt from Visual FoxPro to Visual Basic .NET (ISBN: 0672326493) by Les Pinter appears courtesy of Sams Publishing. Available at http://www.samspublishing.com. Copyright 2004 by Sams Publishing.

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):

  • PUBLIC methods can be called when you instantiate an object based on the class.
  • PROTECTED methods can be seen by other developers in the Class Browser and in the Properties sheet, but can't be invoked except by members of the class itself. You can even select them via IntelliSense, but any attempted call produces an error.
  • HIDDEN methods can't be seen or invoked except by members of the class itself.

When you create an object from a class definition stored in a VCX or PRG using CREATEOBJECT() or NEWOBJECT(), all of the base class properties and methods are exposed. There may be dozens of them. However, if you use a PRG that starts with the statement

DEFINE CLASS <name> AS CUSTOM OLEPUBLIC

and ends with ENDDEFINE and compile it as a single-threaded DLL (STDLL) or a multithreaded DLL (MTDLL), then when you instantiate an object from the resulting DLL, IntelliSense will only show you the PUBLIC properties and methods. It only takes two additional lines of code, and provides a cleaner way for others to use your class's methods.

Function Declarations

But even the many variations on declaring variables discussed in the preceding section don't cause as much confusion as subroutine and function declarations. For one thing, functions and subroutines in FoxPro have always been a bit amorphous; either one can return or not return a value.

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:

FUNCTION DollarsToCharacters (inputval as double) AS String
DollarsToCharacters = CSTR(inputval,10,2)
END FUNCTION

Functions and Procedures in FoxPro
You can define functions and classes either in a PROCEDURE library or a program. Procedure libraries are simply collections of FUNCTION and PROCEDURE blocks stored inline in a text file ending with the extension .prg. They are opened for use as procedure libraries using the statement

SET PROCEDURE TO <filename> ADDITIVE

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.

FoxPro traditionally uses the PARAMETERS statement to collect passed values. More recent versions of FoxPro permit the use of the Visual Basic syntax. So you can now write either

FUNCTION DollarsToCharacters
PARAMETERS InputVal
RETURN STR(InputVal,10,2)
ENDFUNC

or

FUNCTION DollarsToCharacters ( InputVal as Numeric ) AS String
RETURN STR(InputVal,10,2)
ENDFUNC

In FoxPro, that's about all you have to know.

Functions and Subroutines in Visual Basic .NET
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.)

The following is the SUB declaration in Visual Basic .NET:

[ <attrlist> ] [{ Overloads | Overrides | Overridable |
NotOverridable | MustOverride | Shadows | Shared }]
[{ Public | Protected | Friend | Protected Friend | Private }]
Sub name [(arglist)] [ Implements interface.definedname ]
  [ statements ]
  [ Exit Sub ]
  [ statements ]
End Sub

The following is the FUNCTION declaration in Visual Basic .NET:

[ <attrlist> ] [{ Overloads | Overrides | Overridable |
NotOverridable | MustOverride | Shadows | Shared }]
[{ Public | Protected | Friend | Protected Friend | Private }]
Function name[(arglist)] [ As type ] [ Implements interface.definedname ]
  [ statements ]
  [ Exit Function ]
  [ statements ]
End Function

The first group of options in a function or subroutine declaration determine how the routine relates to any identically named function in the base class. An explanation of each of these options follows. However, you probably don't need any of this. I'll explain at the end of this section:
  • 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.
  • Overrides -- This function procedure overrides an identically named procedure in a base class. The number and data types of the arguments, and the data type of the return value, must exactly match those of the base class procedure.
  • Overridable -- This function procedure can be overridden by an identically named procedure in a derived class. This is the default setting for a procedure that itself overrides a base class procedure.
  • NotOverridable -- This function procedure cannot be overridden in a derived class. This is the default setting for a procedure that does not itself override a base class procedure.
  • MustOverride -- This function procedure is not implemented in this class, and must be implemented in a derived class for that class to be creatable. Use this as a reminder to another member of the programming team to build his own function.
  • Shadows -- Like Overrides, except that parameters don't have to match. This means "Ignore the method of the same name in the base class and use this one instead."
  • 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.
  • Public -- No restrictions on the accessibility of public procedures.
  • Protected -- Accessible only from within their own class or from a derived class. Can be specified only on members of classes.
  • Friend -- Accessible from anywhere in the same program.
  • Protected Friend -- Union of protected and friend access. Can be specified only on members of classes.
  • Private -- Accessible only from within their declaration context.

Constructors: Init vs. New
In FoxPro, you can include a PARAMETERS or LPARAMETERS statement as the first line of code in a class's Init procedure, then pass parameters to it in the CreateObject() or NewObject() function call that instantiates an object based on the class. Init is called the class constructor.

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:

Dim da as New SQLDataAdapter("SELECT * FROM Customers",
"server=(local);database=NorthWind, PWD=sa;")

or a SQL select statement and an object reference to an open connection, like this:

Dim cn as New SQLConnection("server=(local);database=NorthWind, PWD=sa;")
Cn.Open()
Dim da as New SQLDataAdapter("SELECT * FROM Customers", cn)

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
Finally, you can declare a function signature (a collection of properties and methods) known as an interface, and then include an Implements clause in your function or subroutine declaration that checks to see that you did it right. It's a sort of project control feature.

Implements -- Indicates that this function procedure implements a function procedure defined by an interface.

If you're asked to add a specific functionality to your class that's been implemented elsewhere, you can build a list of the properties and methods you want to expose and include them in an Interface declaration. You can then include the statement Implements (interface name) in another class, and the compiler will report an error if the class doesn't reference all of the properties and methods in the interface.

For example, define a class called Class1 as shown in Listing 1.1.

LISTING 1.1 Declaring an Interface

Public Class Class1

  Public Interface Foo
    Property Banana() As String
    Sub One(ByVal a As String)
    Function Two(ByVal b As String) As String
  End Interface

End Class

Now, open another class named Class2, and on line 2, type Implements Class1.Foo. (The "Foo" will appear thanks to IntelliSense when you press the period key.) Prototyped declarations for the property, the subroutine, and the function will automatically be inserted into your code (see Listing 1.2).

LISTING 1.2 Implementing an Interface in a Class

Public Class Class2
  Implements Class1.Foo
  Public Property Banana() As String Implements Class1.Foo.Banana
    Get

    End Get
    Set(ByVal Value As String)

    End Set
  End Property

  Public Sub One(ByVal a As String) Implements Class1.Foo.One

  End Sub

  Public Function Two(ByVal b As String) As String Implements Class1.Foo.Two

  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.

Defining interfaces and then using the Implements keyword to check your math is not a bad idea. You can do it in FoxPro as well (when working with Component Object Model [COM] objects), although I've never had occasion to do so. It's helpful, but it's not necessary. So unless someone tells you to use them, ignore Interface declarations and Implements clauses. In a team environment, they become more useful.

Summary of Visual Basic Declarations

The following summary of function scope declarations describes the available options. After I describe them, I'll suggest a simple approach.
  • Public and Friend can refer to any of the following elements: Class, Const, Declare, Delegate, Dim, Enum, Event, Function, Interface, Module, Property, Structure, or Sub; Protected and Private can be used with all of these except Module, which can only be Public or Friend (the default).
  • 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.
  • Shared means that you can refer to the property without instantiating an object based on the class. It allows you to use the class itself as an object based on the class. We don't have that in FoxPro.
  • Shadows means that a variable completely replaces the identically named element in a base class. This is the default in FoxPro: If you write code in the method code window of a derived form class, it completely replaces the code that was written in the base form class. FoxPro doesn't check whether the signatures match or not. Ever.

So What Should I Use?
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 values -- for 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.

A footnote on overloaded methods in .NET
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:

Dim cn As New SQLClient.SQLConnection(

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 )

I could base a form on this "form template," assign a value to the MainTable property, and the form would run as expected. Gradually, most if not all of the code in the original form migrated to the class. I ended up with a form that did what I needed if I set a dozen or so properties; if I needed a similar form, I just used

Create form xxx as MyForm FROM PinterLIB

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.

Visual Basic .NET is similarly object-oriented.

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

DEFINE CLASS Foobar AS CUSTOM (OLEPUBLIC)

and ends with

ENDDEFINE

The OLEPUBLIC keyword is required if you want to compile to a DLL, which is required for Web Services applications, and is generally not used by FoxPro developers unless they use COM+.

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

SET CLASSLIB TO VCXNAME ADDITIVE or
SET PROCLIB TO CLSLIB.PRG ADDITIVE

followed by

oName = CREATEOBJECT ( "ClassName" )
oName2 = CREATEOBJECT ( "OtherClass" )

or you can skip the SET CLASSLIB and SET PROCLIB statements and use a shorter approach:

oName = NEWOBJECT ( "ClassName", "VCXNAME.VCX" )
oName2 = NEWOBJECT ( "OtherClass", "CLSLIB.PRG" )

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.

In Visual Basic .NET, classes are defined in code. There is no Visual Class Library. However, if the class is a form you can use the Form Designer to build it visually; and if it's a user control, you get a little container in which your control component or components appear.

The use of namespaces is confusing to FoxPro developers. Namespaces are just there to add an additional level of naming. For example, the class library shown in Listing 1.3 defines two classes in Visual Basic .NET. Note that the project name for this class library is NameSpaceDemo. This is important because the name of the project is the default name of the generated DLL, which becomes the first part of the class name.

LISTING 1.3 Namespace Declarations

Imports System.IO

Namespace PinterConsulting

  Public Class Functions
    Private _FileName As String
    Private sep As Char

    Public Property FileName()
      Get
        Return _FileName
      End Get
      Set(ByVal Value)
        _FileName = Value
      End Set
    End Property

  End Class

  Public Class Objects

    Public Class Person
      Private _FirstName As String
      Private _LastName As String
      Private sep As Char

      Public Property FirstName() As String
        Get
          Return _FirstName
        End Get
        Set(ByVal Value As String)
          _FirstName = Value

          Select Case _FirstName
            Case "Bob"
            Case "Fred"
            Case "Eddie"
            Case Else
          End Select
        End Set
      End Property

      Public Property LastName() As String
        Get
          Return _LastName
        End Get
        Set(ByVal Value As String)
          _LastName = Value
        End Set
      End Property

      Public ReadOnly Property FullName()
        Get
          Return FirstName + " " + LastName
        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:

LISTING 1.4 Using a Namespace in an Imports Statement

Imports NameSpaceDemo.PinterConsulting
Public Class Form1

Inherits System.Windows.Forms.Form

Dim NameMgr As New
PinterConsulting.Objects.Person

Private Sub Form1_Load( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles MyBase.Load
End Sub

Private Sub txtFirst_TextChanged( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles txtFirst.TextChanged
    NameMgr.FirstName = txtFirst.Text
    Label1.Text = NameMgr.FullName
End Sub

Private Sub txtLast_TextChanged( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles txtLast.TextChanged
    NameMgr.LastName = txtLast.Text
    Label1.Text = NameMgr.FullName
End Sub

Private Sub txtFileName_TextChanged( _
  ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles txtFileName.TextChanged
    Dim o As New PinterConsulting.Functions
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?

Instantiating objects in Visual Basic .NET

One thing that will come up constantly in .NET is the creation and use of objects based on .NET classes. For example, the following code opens a table and displays its contents in a datagrid (the .NET equivalent of FoxPro's BROWSE):

Dim cn As New SqlConnection("Server=(local);Database=Northwind;UID=sa;PWD=;")
cn.Open()
Dim da As New SqlDataAdapter("SELECT * FROM CUSTOMERS", cn)
Dim ds As New DataSet
da.Fill(ds)
DataGrid1.DataSource = ds.Tables(0)

The statement Dim cn As SQLConnection is like FoxPro

LOCAL cn As String

It doesn't create the variable. It just announces your intention to do so at some future date. Sounds goofy, but it's true. (It actually feeds IntelliSense - in both cases.) It's supposed to be followed by the statement that actually creates the variable. It can either be done with a New statement, or by referring to a member of an existing object. If you leave out the word New in the Dim cn statement above, you won't be able to pass the connection string in as a parameter. In the fourth line of the same code, you can leave out the word New without incurring a syntax error, but the following line will bomb because Fill only works with an existing Dataset, and the statement Dim ds As Dataset doesn't create one -- it only declares one.

Property Procedures in Visual Basic .NET

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
OModem.Action = 4 ' Read

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.

The tradition continues. If you type

PUBLIC PROPERTY FullName AS String

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
  Set(ByVal Value As String)

  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:

Private _FullName As String
Public Property FullName() As String
  Get
    Return _FullName
  End Get
  Set(ByVal Value As String)
    _FullName = Value
  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.

However, in Visual FoxPro property procedures (that is, Assign and Access methods) are completely optional; I've only had occasion to use them twice in years of client development.

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:

Dim frm as new CustomerForm ' can be written as 2 lines of code
Frm.Show

In FoxPro you just write

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 = CREATEOBJECT ( "FormClassName", "VCXFILE.VCX" )
Frm.Show()

So they're more similar than you might have thought.

Forms have a section of code called the "Windows Form Designer generated code." When you're in the form designer, things that you do, like adding controls on the form, moving things around, and assigning values to properties, are actually stored in source code. The designer merely uses this code to create a visual display of the code's contents. If you manually change this code, it will be overwritten whenever a change is made in the designer and the file is saved.

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.

Events

For a FoxPro developer, events constitute one of the strangest things in Visual Basic .NET. I looked for the equivalent in FoxPro for the longest time, and I just couldn't find it. It turns out that there are two kinds of events in Visual Basic .NET, and they bear little resemblance to one another. FoxPro only has one kind of event. Visual Basic actually uses events to call methods in other objects; hence the confusion. I hope this part of the chapter will clear it up for you.

Events in Visual FoxPro

FoxPro's classes have properties, events, and methods. Properties are variables, and methods are functions and procedures. But you can't add events. When you subclass a base class, you can add properties and methods at will. You can also add your own code to the existing methods. For example, when you refresh a form, you can add code to SEEK() records in all related tables so that bound fields displayed on the form will be coordinated.

Events are different. Events happen. When they do, any code that you add to the event's code window is executed, in addition to the default behavior of the event. But events happen when they happen -- when you click, or double-click, or mouse over, or whatever. You don't add events in FoxPro.

Each of FoxPro's base classes comes with a generous but fixed list of events. You can't add to FoxPro's events; you're stuck with the list of events that each class was born with. In FoxPro, the name of an event implies what it handles. TextBox1.Click tells you which object the Click event responds to. There is no Handles clause at the end. It's not needed. You don't even see the mechanism that handles it. It's transparent.

To add code for an event, you pick the event from the Method Name/Members combo box of the code window navigation bar (at the upper-right side of the screen).

FoxPro's IDE supplies any necessary parameters. The code, in the form of an LPARAMETERS statement, is inserted when the code window opens. If you erase the LPARAMETERS line and leave the code window empty, the LPARAMETERS line will be put back in the next time you open the code window. For example, open a new MouseDown event code window and you get this:

LPARAMETERS nButton, nShift, nXCoord, nYCoord

NOTE
We'll look at the event argument parameter generated for this same event in Visual Basic by the .NET IDE in the next section. It does essentially the same thing.

There is no Event statement in FoxPro. (There are three new functions [BindEvent, RaiseEvent and ReleaseEvents] related to events in Visual FoxPro8, but it's difficult to come up with a reason to use them.) Looking at the list of events for a typical FoxPro control, I can't imagine why I'd want to add more events. That's why the Event statement in Visual Basic has always confused me. And why "raise" an event? Aren't events things that just happen, like "Click"?

NOTE
The closest things we have to events in FoxPro are the Assign and Access methods for form properties. They allow you to trap the instant when a value is assigned to or retrieved from a class property. But that's unrelated to the use of RaiseEvents in Visual Basic. It's interesting that in FoxPro we use the Visual Basic naming convention. If you add a MyPropName_Assign method to your class, the code in it will fire when the property is assigned a value.

This is at the heart of the reason that FoxPro developers can't easily understand Visual Basic events. The events that are declared for the purpose of calling them with RaiseEvent have nothing to do with the events that come with controls. That's not what RaiseEvent is used for in Visual Basic.

Events in Visual Basic .NET

One of the really huge differences between FoxPro and Visual Basic .NET is Visual Basic's use of events. FoxPro objects have events, and you can write code in the event snippets that will run when the event fires. It's transparent. In Visual Basic, event handling is not transparent. You get to watch.

If you create a Windows Form project and add a text box to the form, you'll find the following generated code in the form's codebehind:

Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
...
'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)

If the WithEvents clause were not included, the program would ignore any of the control's events. Presumably, doing so causes the program to run faster; otherwise, why would the WithEvents clause exist? This allows us to add a Handles clause to a subroutine, regardless of its name, that "reacts" when the named event occurs. So what is the Visual Basic default -- without events? Oddly enough, it is.

The IDE writes the code for you if you open the code window and select an object from the Objects drop-down list at the upper-left corner of the window, and then select an event from the drop-down list at the upper-right corner of the same window.

For example, the following code changes the text box back to black text on a white background when the cursor leaves the control:

Private Sub TextBox1_LostFocus( _
  ByVal sender As Object, _
  ByVal e As System.EventArgs) _
    Handles TextBox1.LostFocus
  TextBox1.ForeColor = Color.Black
  TextBox1.BackColor = Color.White
End Sub

Although a subroutine name that references the event name is generated when you add the code, the name is in fact functionally unrelated to what the routine does. You can change the name, and the program will work exactly the same. The Handles clause at the end is what causes the subroutine to execute when the event fires. Or, as they like to say in Redmond, when the event is raised.

RANT
<rant>That's actually part of the problem as well. The guys who developed Visual Basic took considerable liberties with the language. You throw and catch an error. (Why? Because they had seen the Mariners the night before, and because they could. There were no language police. The first little guy high on Coca-Cola at three a.m. gets to pick a name.) Similarly, you raise an event. Don't even get me started on persisting, depersisting, and consuming XML. By capriciously assigning terms that don't particularly reflect what's being done, they made the language that much harder to understand. But I digress... </rant>

The two parameters in parentheses that follow the subroutine name are always ByVal sender As Object (a reference to the calling object) and an event argument whose type is based on what kind of parameters the called event needs. For example, if you add a MouseDown event routine, you get the following code:

Private Sub Form1_MouseDown( _
    ByVal sender As Object, _
    ByVal e As System.Windows.Forms.MouseEventArgs) _
  Handles MyBase.MouseDown
  Debugger.Break ' like SET STEP ON
End Sub

I've included a breakpoint so that you can type "e" into the Watch window and see what it is. It's a structure containing the values needed to respond to a mousedown event: X and Y coordinates, among others. So it's pretty much the same thing that FoxPro is doing with its generated LPARAMETERS statements. One more mystery solved.

If this were the only use for events in Visual Basic .NET, you could simply ignore them and let the IDE generate event-related code whenever it felt like it. The developers of FoxPro apparently felt that the confusion resulting from optionally exposing events in this way was not worth the bother. Generally, they were correct.

RaiseEvent
Events in Visual Basic are also used as a workaround for a characteristic of the Visual Basic compiler that prevents it from calling functions and procedures that aren't resolved at compile time. In particular, although you can instantiate an object based on a class and call the object's methods at will from within a form, you can't do the opposite in Visual Basic. For example, in FoxPro, you can't call THISFORM.Refresh from inside a class, like this:

DEFINE CLASS Utilities AS CUSTOM

PROCEDURE CoordinateTables
THISFORM.Refresh && in whatever form is active
ENDPROC

ENDDEFINE

You can, however, use _Screen.ActiveForm.CoordinateTables. You can even use PEMSTATUS ( Object, MethodName, 5 ) to determine whether the method exists before calling it. So we have workarounds in FoxPro. But in Visual Basic they don't. (Well, there is reflection, but it's new. RaiseEvents has been around since Visual Basic4 at least, so that's how they do it.)

In Visual Basic you can't call a method in a form from inside an object without resorting to some trick. RaiseEvent is that trick. That's why it's so confusing. Here we are trying to think up events that might go beyond clicking or double-clicking. The use of RaiseEvent is a trick with mirrors to overcome a limitation in the compile process for Visual Basic. You don't need to try to invent events in FoxPro. There's no reason to. You can do what you need to do without them. In Visual Basic, you can't.

This is why FoxPro developers have a hard time understanding events in Visual Basic. All of FoxPro's events are always trappable; there is no need to use WithEvents to declare that the events for a control are subject to handling; they just are. And the second reason for using events in Visual Basic is unnecessary in Visual FoxPro!

In Visual Basic, Public Sub XXX Handles ObjectName.EventName can be used to respond to the line RaiseEvent EventName in a class. So a form can contain a subroutine that is called when the event named in the Handles clause of the subroutine fires (or is raised, as they like to say).

How to Declare an Event in a Class

To declare an event in a class, include the statement

Public Event <Name> ( Parameters )

Instantiate your object:

Dim oObj as New <ClassName>

Then, within the code, invoke it using the RaiseEvent command:

RaiseEvent <Name> ( Parameters )

This "raises" the event and passes any parameters to it. Finally, trap the event using the Handles clause of a subroutine in the form:

Public sub xxx ( Parameters as string ) Handles oObj.EventName

Using this mechanism, I've created a "hook" that lets me call any method in the form when something happens in the class. Note that I could simply call a method in the class using oObj.MethodName ( Parameters ), as I do in the Update button code in Listing 1.5. But if I want to call form methods from the class, this is actually a pretty clean way to do it.

Here's an example, based on the "Visual Basic .NET How-To N-Tier Data Form App" sample included in the "101 Visual Basic .NET Samples" library available for free from the MSDN site. I can't say enough about this code library, which gives you good examples of how to do a huge variety of things in Visual Basic .NET. I've added a few properties so that it's more flexible and easier to configure.

Using Events in a Data Access Class
The benefits of separating the implementation of data access from the form are immediate. The data access class is external to the form, so you can change the data source without making any changes to the form. This allows (for example) using MSDE during development, then changing to SQL Server at a later date with minimal disruption. Listing 1.5 shows the example class.

LISTING 1.5 A Data Access Class

Option Strict On
Imports System.Data.SqlClient

Namespace DataAccessLayer

Public Class DataAccess

  Protected Const CONNECTION_ERROR_MSG As String = "Couldn't connect to SQL"
  Protected Const SQL_CONNECTION_STRING As String = _
"server=(local);database=Northwind;uid=sa;pwd=;"

Public da As SqlDataAdapter
Public ds As DataSet

Public _MainTable As String
Public _KeyField As String

Protected DidPreviouslyConnect As Boolean = False
Protected strConn As String = SQL_CONNECTION_STRING

Public Event ConnectionStatusChange(ByVal status As String)
Public Event ConnectionFailure(ByVal reason As String)
Public Event ConnectionCompleted(ByVal success As Boolean)

Public Property MainTable() As String
  Get
    Return _MainTable
  End Get
  Set(ByVal Value As String)
    _MainTable = Value
  End Set
End Property

Public Property KeyField() As String
  Get
    Return _KeyField
  End Get
  Set(ByVal Value As String)
    _KeyField = Value
  End Set
End Property

Public Function CreateDataSet() As DataSet

  Dim ds As DataSet
  If Not DidPreviouslyConnect Then
    RaiseEvent ConnectionStatusChange("Connecting to SQL Server")
  End If
  Dim IsConnecting As Boolean = True
  While IsConnecting
    Try
      Dim scnnNW As New SqlConnection(strConn)
      Dim strSQL As String = "SELECT * FROM " + MainTable

        Dim scmd As New SqlCommand(strSQL, scnnNW)
        da = New SqlDataAdapter(scmd)
        Dim cb As New SqlCommandBuilder(da)
        ds = New DataSet
        da.Fill(ds, MainTable)
        IsConnecting = False
        DidPreviouslyConnect = True
      Catch exp As Exception
        If strConn = SQL_CONNECTION_STRING Then
          RaiseEvent ConnectionFailure(CONNECTION_ERROR_MSG)
        End If
      End Try
    End While
    RaiseEvent ConnectionCompleted(True)
    da.Fill(ds, MainTable)
    Return ds

  End Function

  Public Sub UpdateDataSet(ByVal inDS As DataSet)
    If inDS Is Nothing Then
      Exit Sub
    End If
    Try
      If (da Is Nothing) Then
        CreateDataSet()
      End If
      inDS.EnforceConstraints = False
      da.Update(inDS, MainTable)
    Catch exc As Exception
      RaiseEvent ConnectionFailure("Unable to update the data source.")
    End Try
  End Sub
End Class
End Namespace

There are four RaiseEvent calls in this code. Each call is made when the result of an attempt to connect to the database is known. Because you don't know which form is going to call these methods, or even what the form is going to do when this occurs, all you provide is the mechanism for notifying the form that it needs to do something.

This is called messaging. Internally, Windows threads send each other messages. Each thread listens for a message, and responds when the message is received.

RANT
<rant>There was once a book written about FoxPro that went on and on about "messaging." I thought it was silly. When I start my car, it causes thousands of gasoline explosions per second. But I don't say "I'm going to explode some gasoline." I say "I'm going to drive to the store." The less I know about what's under the hood, the better. In fact, the one criticism I have of .NET at this point is that it gives me way too much information. I can't imagine why I need to see the code that instantiates text boxes and positions them on the form.</rant>

So in summary, the stage is set for the class code to "call" related methods in a form to be named later. In the form code shown in Listing 1.6, you'll see how this is done.

The Form

The form code answers the question "What do I do when these events occur?" The Data Access Layer class is instantiated as object m_DAL. The events are referred to with the prefix m_DAL because they belong to the object. See Listing 1.6.

LISTING 1.6 A Form That Uses a DAL Component with Events

Option Strict On
Imports System.Data.SqlClient
Imports DataAccessLayer

Public Class frmMain
  Inherits System.Windows.Forms.Form

...Windows Form Designer generated code goes here...

  Protected DidPreviouslyConnect As Boolean = False
  Private ds As DataSet
  Private dt As DataTable
  Private dv As DataView

  Public MainTable As String = "Customers"
  Public KeyField As String = "CustomerID"

  Protected WithEvents m_DAL As DataAccess

  Dim frmStatusMessage As New frmStatus

  Private Sub btnNext_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles btnNext.Click
      NextRecord()
  End Sub

  Private Sub btnPrevious_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles btnPrevious.Click
      PreviousRecord()
  End Sub

  Private Sub btnRefresh_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles btnRefresh.Click
      frmMain_Load(Me, New System.EventArgs)
  End Sub

' Call the UpdateDataSet method:
  Private Sub btnUpdate_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles btnUpdate.Click
m_DAL.UpdateDataSet(ds.GetChanges())
      frmMain_Load(Me, New System.EventArgs)
  End Sub

  Protected Sub dt_PositionChanged( _
    ByVal sender As Object, _
    ByVal e As System.EventArgs)
      BindGrid()
  End Sub

  Private Sub frmMain_KeyDown( _
    ByVal sender As Object, ByVal e _
    As System.Windows.Forms.KeyEventArgs) _
    Handles MyBase.KeyDown
      If e.KeyCode = Keys.Right Then NextRecord()
      If e.KeyCode = Keys.Left Then PreviousRecord()
  End Sub

  Private Sub frmMain_Load( _
    ByVal sender As Object, _
    ByVal e As System.EventArgs) _
    Handles MyBase.Load
      frmStatusMessage = New frmStatus
      GetDataSet()
      BindGrid()
  End Sub

  Private Sub grd_CurrentCellChanged( _
    ByVal sender As Object, _
    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 sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles grd.Click
      BindGrid()
  End Sub

* Handle the ConnectionCompleted event:
  Private Sub m_DAL_ConnectionCompleted( _
    ByVal success As Boolean) _
    Handles m_DAL.ConnectionCompleted
      frmStatusMessage.Close()
  End Sub

* Handle the ConnectionFailure event:
  Private Sub m_DAL_ConnectionFailure( _
    ByVal reason As String) _
    Handles m_DAL.ConnectionFailure
      MsgBox(reason, MsgBoxStyle.Critical, Me.Text)
      End
  End Sub

* Handle the ConnectionStatusChanged event:
  Private Sub m_DAL_ConnectionStatusChange( _
    ByVal status As String) _
    Handles m_DAL.ConnectionStatusChange
      frmStatusMessage.Show(status)
  End Sub

  Sub BindGrid()
    With grd
      .CaptionText = MainTable
      .DataSource = dv
    End With
  End Sub

  Sub GetDataSet()
    frmStatusMessage.Show("Retrieving Data From Data Access Layer")
    ds = m_DAL.CreateDataSet()
    dt = ds.Tables(MainTable)
    dv = dt.DefaultView
  End Sub

  Public Sub NextRecord()
    grd.UnSelect(grd.CurrentRowIndex)
    grd.CurrentRowIndex += 1
    grd.Select(grd.CurrentRowIndex)
  End Sub

  Public Sub PreviousRecord()
    grd.UnSelect(grd.CurrentRowIndex)
    If grd.CurrentRowIndex > 0 Then
      grd.CurrentRowIndex -= 1
    End If
    grd.Select(grd.CurrentRowIndex)
  End Sub

End Class

frmStatus is just a simple form with a label control in the center of the screen and an override of the form's Show method code:

Public Overloads Sub Show(ByVal Message As String)
  lblStatus.Text = Message
  Me.Show()
  System.Threading.Thread.CurrentThread.Sleep(500)
  Application.DoEvents()
End Sub

This displays the form with a message and resumes execution after a half-second delay.

To summarize: If you want to use events in your classes to call methods in forms when you don't yet know which form and which methods, do this:

1. Create a solution containing a project named, say, "A", output type "Class Library", which contains a class named, say, "B".

2. In class B, define an event like this:

PUBLIC Event ABC ( ByVal xxx As String )

3. In the class, call using this:

RaiseEvent ABC ( parms )

4. Add a Windows Form project.

5. In the form, include this:

Imports A 'so that it will know to use the class

6. Add this:

Protected Withevents C AS B 'instantiate an object based on the class

7. In the form, add a subroutine or function with a Handles clause:

Private Sub XYZ ( parms ) Handles C.ABC

8. Use the sub/function's parameter list to pass the event's parameters.

Compiler Directives

In FoxPro, the #CONST directive can be used to specify a value that is substituted for occurrences of the named constant in the code at compile time. The #IF #ELSE #ENDIF construct can be used to deliver source code that will include only the code that will compile correctly given the compiler version. #DEF, #UNDEF, and #IFDEF can similarly be used to determine what code gets compiled. The #Include directive reads in a text file, and is like the #ExternalSource directive in Visual Basic.

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.

The most interesting compiler directive in Visual Basic is the #Region Name/#End Region block, which allows you to group code elements and collapse them to a single label by clicking on the "+" beside the #Region directive. There's no equivalent in FoxPro, although the way that FoxPro groups code into snippets in the Form and Class Designers provides similar functionality, in the sense of letting you see only some of the code at one time.

Table 1.8 summarizes compiler directives in Visual FoxPro and Visual Basic.

TABLE 1.8 Compiler Directives
ActionVisual BasicVisual FoxPro
Define a constant#Const#Const
Compile selected lines#IF.. #ENDIF#IF.. #ENDIF
Collapse part of code#Region.. #End RegionNo equivalent
Include a file#ExternalSource#Include

Data

Perhaps the greatest difference between Visual FoxPro and Visual Basic .NET is the handling of tables. It's a huge topic, and I'll just talk about general issues here. (It's covered in considerably more detail in Chapter 4, "A Visual Basic .NET Framework for SQL Server," and Chapter 7, "XML.") But I can give you a feel for the issues here.

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” standalone=”yes”?>
<VFPData>
  <xsd:schema id=”VFPData” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” _
    xmlns:msdata=”urn:schemas-microsoft-com:xml-msdata”>
      <xsd:element name=”VFPData” msdata:IsDataSet=”true”>
        <xsd:complexType>
          <xsd:choice maxOccurs=”unbounded”>
            <xsd:element name=”customer” minOccurs=”0” maxOccurs=”unbounded”>
              <xsd:complexType>
                <xsd:sequence>
                  <xsd:element name=”name”>
                    <xsd:simpleType>
                      <xsd:restriction base=”xsd:string”>
                        <xsd:maxLength value=”20”/>
                      </xsd:restriction>
                    </xsd:simpleType>
                  </xsd:element>
                  <xsd:element name=”phone”>
                    <xsd:simpleType>
                      <xsd:restriction base=”xsd:string”>
                        <xsd:maxLength value=”12”/>
                      </xsd:restriction>
                    </xsd:simpleType>
                  </xsd:element>
                </xsd:sequence>
              </xsd:complexType>
            </xsd:element>
          </xsd:choice>
          <xsd:anyAttribute namespace=”http://www.w3.org/XML/1998/
. namespace” 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.


FIGURE 1.6 The same data 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.

In FoxPro, on-screen controls use the ControlSource to bind to the data. Binding is bidirectional; change the contents of a text box, and the changes are automatically written back to the DBF. (There are a few caveats to observe, especially in multiuser environments, but this is basically how it works.) Grids have a RecordSource property that binds data to the grid in the same way that ControlSource does for controls.

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

This assumes that you previously dropped a SQLDataAdapter on the form and specified the connection to use to the ensuing wizard, and then right-clicked on the DataAdapter and selected Generate DataSet from the context menu, providing the name dsEmployee as the dataset name. Visual Basic .NET adds a 1, because it always adds a 1, even to the first instance of the class.

However, binding is one-way. In order to update the data source from whence the data came, you'll have to add code to get the changes made to the dataset and put them into a diffgram. Then you'll have to pass the diffgram to the SQLDataAdapter's Update method, which was generated automatically when the DataAdapter was created. It's just a few more lines of code, but the fact that in FoxPro it's not necessary is notable.

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.

The dataset's AcceptChanges and RejectChanges methods correspond to FoxPro's TableUpdate() and TableRevert() functions, respectively. But that still doesn't get your data back to the data store. Think of the dataset as a cursor into which you've copied a record. You've saved the changes to the cursor, but you haven't copied the changed record back to wherever it came from. How you do that will have to wait until Chapter 4.

Error Handling and Debugging

After years of primitive error handling, both Visual Basic and Visual FoxPro have finally acquired real error-handling capabilities with the TRY...CATCH block. And Visual Basic .NET actually has more debugging features than does FoxPro.

TRY...CATCH
The syntax for TRY...CATCH is slightly different for Visual Basic .NET than for Visual FoxPro.

Here's the syntax in Visual FoxPro:

TRY
  Code here
Catch To oEx WHEN ERROR() = 1234
  What to do for this error
Catch To oEx WHEN ERROR() = 4321
  What to do for this error
Catch To oEx
  What to do for all other errors
Finally
  Do this whether there was an error or not
ENDTRY

At a minimum, you can print out the error details:

Msg = oEx.Message + CHR(13)+oEx.Details + CHR(13)+oEx.LineContents
MessageBox ( Msg, 32, "Error" )

And this is the syntax in Visual Basic .NET:

Try
  Code here
Catch oEx as ExceptionType1 (of about 60 exception types)
  Code to handle ExceptionType1
Catch oEx as ExceptionType2 (of about 60 exception types)
  Code to handle ExceptionType2
Catch oEx as Exception
  Code to handle all other Exceptions
Finally
  Do this under all circumstances
END Try

Similarly, you can print out oEx.Message + CHR(13)+oEx.Source to see the error message and offending code.

Visual Basic does have additional language elements (Error, Raise, GetException, Err, On Error, and Resume), but Try...Catch is a lot better.

Error trapping is meant to allow graceful handling of unavoidable errors in the program, due to device unavailability (CD-ROM, printers, floppy drives), incorrect input that can't be prevented, and other errors that are the result of normal program operation. Error trapping is not meant to be used to catch errors in program logic. Still, it's better than letting users see messages from the FoxPro or the .NET IDE.

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

Debugging support is awesome in both languages. In FoxPro, you can either toggle a breakpoint by clicking in the selection margin of the code at the point where you want the code to break, or you can insert a SET STEP ON command. In Visual Basic .NET, you can either toggle a breakpoint by clicking in the selection margin of the code at the point where you want the code to break, or you can insert a Debugger.Break command.

FoxPro Debugging Aids

At this point FoxPro provides you with five windows:

  • Trace (to step through the code)
  • Locals (all variables and objects)
  • Watch (type in the names of the variables to inspect)
  • Call Stack (A called B called C, and so on)
  • Debug Output (Debug.print output goes here)

In addition, you can add assertions to your code. Assertions are statements that only execute if they evaluate to False, and only if you SET ASSERTS ON. You'll get a little dialog window that asks if you want to debug. This permits bracketing of debugging code in a way that it can be turned on and off with a single command in the command window.

Visual Basic .NET Debugging Aids

In Visual Basic .NET, Debugger.Break (or a breakpoint entered in the Selection Margin of the code window) gives you a vast array of debugging windows:
  • Modules
  • Threads
  • Call Stack
  • Me
  • Command window
  • Locals
  • Autos
  • Watch (four of them!)
  • 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.

In Chapter 2 we'll build a FoxPro application using form classes and a data access class, permitting us to switch between DBFs and SQL Server as the data store. This application will serve as a model for developing your first Visual Basic .NET application in Chapter 3, using very similar coding. Then in Chapter 4 we'll compare data access to tables, SQL Server, and XML Web Services, in preparation for upgrading our applications to work with any of the three types of data stores.


Differences Between Visual FoxPro and Visual Basic .NET: Part 2

8 reader comments:

What do YOU think about this topic? Share your advice and thoughts using this form.

Your Name

REQUIRED : PUBLIC

Your E-Mail

REQUIRED : PRIVATE

Job, Company

OPTIONAL : PUBLIC

City, State, Country

OPTIONAL : PUBLIC

Your Web Site

OPTIONAL : PUBLIC

Your Comment

Please help everyone by keeping your comments on-topic, using clean language, and not defaming or making personal attacks.


Your e-mail address is required, but it will not be displayed to the public or given to anyone. See our Privacy Policy. Comments become visible after they pass our spam filter, and spammers and abusers are permanently blocked. Please report spam or abuse.

Les Pinter is contributing editor of Universal Thread magazine in Montréal and a monthly contributor to the Virtual FoxPro User Group (VFUG) newsletter. He is a member of the INETA Speakers' Bureau and frequently speaks on database development around the world. He gives seminars on Visual FoxPro and Visual Basic database development in English, French, Spanish, Portuguese, and Russian. Les was selected as a Microsoft Visual FoxPro MVP in fall of 2003.

Printer-friendly
page layout

Keyword Tags: Application Development, Code, Database Development, Development, Microsoft, Microsoft Visual Basic, Microsoft Visual Basic .NET, Microsoft Visual FoxPro, Microsoft .NET, Microsoft .NET Framework, Programming

ADVISORAMA
We have already hunted the grey whale into extinction twice.
-- Andrea Arnold

ARTICLE INFO

DataBased Advisor

Web Edition: 2004 Week 31, Doc #14438

FREE ACCESS FREE ACCESS

SUBSCRIPTION STATUS
You are not signed-in. If you are a subscriber to this publication, sign-in above to access locked articles. To subscribe or renew go to www.AdvisorStore.com.