< Day Day Up > |
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 DeclarationsImports 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 StatementImports 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 .NETOne 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 .NETFor 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. FormsForms 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. |
< Day Day Up > |