Wednesday, July 20, 2005
Some Interesting Stats...
Microsoft recently posted some interesting stats about VFP developers.
One interesting set of data looks at the current
group of VFP developers and what other products they use:
- 20% of VFP developers also use VB6, 13% VB.NET, 12%
use C#, 10% use Java, and 8% use C++
- 26% of VFP developers also use VS.NET, 21% ASP.NET
So at least about a quarter of VFP developers (not counting people who have moved off to .NET)
are also using .NET in some form. This means that close to three quarters of the
current VFP community has not yet adopted .NET. However, a lot of them are
generally interested in .NET, as the following stat shows:
- 31% of current VFP developers who have not looked into .NET yet are
planning to do so within the next 2 years
Or on other words: About 40% of the remaining three quarters plan to adopt .NET
in the next 2 years. This will bring us to a total of 57% (or more) of current VFP
developers using .NET within 2 years. This is very interesting, because it means
that counting the people who used VFP 12 months ago but have now moved to .NET
(which are not included in the above numbers), the majority of VFP
developers will adopt .NET. (Well, even not counting those that have
already moved, this statement is still true).
What is also interesting (and cool) is that 98% of the current VFP developers
(and this also includes EPS!) predict that they will still use VFP 12 months
from now in some capacity (for EPS, this probably applies much longer).
VFP developers also seem very open minded when it comes to databases:
- 55% use SQL Server
- 22% use MSDE (the "small version of SQL Server")
- 21% use MySQL (the "open source equivalent to SQL
Server")
- 14% use Oracle
- 4% use DB2
One could interpret this as "almost all VFP developers use a non-VFP
database". However, while there seems to be some truth to that (most large
apps certainly seem to be using a dedicated database server product these days),
a full 89% still use DBF files, at least in addition to other databases (if not
exclusively).
At EPS and VFPConversion.com, we think these are pretty amazing and
encouraging numbers. Adopting technologies like .NET and SQL Server in
combination with Visual FoxPro certainly makes for a very powerful
toolbox!
Posted @ 10:25 PM by Egger, Markus (megger@eps-software.com) - Comments
Monday, July 18, 2005
Exposing strongly-typed VFP COM objects to .NET
VFP is a weakly-typed language by nature,
and that can cause problems when consuming VFP objects from .NET through COM
Interop. One way to improve the developer’s experience on the .NET side is by
adding type information in the VFP code to make the VFP object a "little more
strongly typed" (even though VFP still won’t enforce type-safety internally,
allowing developers to do things like assigning logical values to string
properties from within VFP ). Here are some things one can do to make exposed
elements more usable in .NET:
- Specify the type of method parameters,
as well the type of the return value
- Make use of the COM_Attrib array in
order to provide information that gets generated in the object's type-library
describing properties exposed by the object.
- Create well-defined interfaces that
describe what’s exposed by other objects that are used by the COM object.
For instance, consider the simple Employee
business object:
Define Class Employee As Biz Of Biz.prg
OlePublic
Implements
Iemployeedata In "DataObjectInterfaces.EmployeeData"
*-- Keeps a Reference To the Data Object.
oData = Null
*-- Properties For Error handling
Info.
lError = .F.
cErrorMsg = ""
Procedure GetEmployeeByPK(String
employeePK) As Boolean
*-- Run
whatever Code populates the oData Member.
This.oData =
Createobject("Empty")
AddProperty(This.oData, "EmployeeID", 99)
AddProperty(This.oData, "EmployeeName", "Jane Doe")
Endproc
Protected Procedure
Iemployeedata_get_EmployeeID() As Number;
HelpString
"Employee ID"
Return
This.oData.EmployeeID
Endproc
Protected Procedure
Iemployeedata_put_EmployeeID(eValue As Number @);
HelpString "Employee
ID"
This.oData.EmployeeID = eValue
ENDPROC
Protected Procedure
Iemployeedata_get_EmployeeName() As String;
HelpString
"Employee Name"
Return
This.oData.EmployeeName
ENDPROC
Protected Procedure
Iemployeedata_put_EmployeeName(eValue As String @);
HelpString
"Employee Name"
This.oData.EmployeeName = eValue
Endproc
*-- Property's attributes for COM
interface.
Dimension
lError_COMATTRIB[4]
lError_COMATTRIB[1] = COMATTRIB_READONLY
lError_COMATTRIB[2] = "Indicates
whether an error has occurred inside the object."
lError_COMATTRIB[3] = "lError"
lError_COMATTRIB[4] =
"Boolean"
Dimension
cErrorMsg_COMATTRIB[4]
cErrorMsg_COMATTRIB[1] = COMATTRIB_READONLY
cErrorMsg_COMATTRIB[2] = "Info
about last error."
cErrorMsg_COMATTRIB[3] = "cErrorMsg"
cErrorMsg_COMATTRIB[4] =
"String"
Dimension oData_COMATTRIB[4]
oData_COMATTRIB[1] = .F.
oData_COMATTRIB[2] = "Keeps
master data object."
oData_COMATTRIB[3] = "oData"
oData_COMATTRIB[4] = "IDispatch"
Enddefine
Note that:
- The GetEmployeeByPK method defined one parameter of type string, and the
return type is a Boolean (logical value in VFP).
- COM Attrib arrays are created for public properties, specifying more
information about each one of them (that information is used when the
type-library for the COM object is created).
- The class exposes an oData member, which is created on the fly. In order
to expose that in a strongly-typed manner, the class implements an
IEmployeeData interface (listed below), which in turn determines what members
are exposed by the oData object.
The IEmployeeData interface is created off of the following class:
// Declare variable and create business object.
EmployeeClass oBiz = new
EmployeeClass();
// Call method to retrieve
employee.
if
(oBiz.GetEmployeeByPK(employeeID))
{
// Get a reference to the business object as a data
interface.
Iemployeedata oData = oBiz as Iemployeedata;
// If we got the reference properly, we can start using the
data.
if (oData !=
null)
{
this.txtEmployeeID.Text = oData.EmployeeID;
this.txtEmployeeName.Text = oData.EmployeeName;
}
}
An article about this will soon be posted to the www.vfpconversion.com
web-site.
Posted @ 12:19 PM by Lassala, Claudio (lassala@foxbrasil.com.br) - Comments
Monday, July 11, 2005
Why Interfaces Are Important
.NET supports the concept of interfaces in addition to classes. So does VFP.
However, in VFP interfaces are only required in special scenarios, while in
.NET, they are used all the time. This is something VFP developers need to get
used to.
But why is that exactly? There are a number of reasons why interfaces are
incredibly important and useful. This post represents just one example scenario.
Before we look at that, we need to address the question "what exactly is an
interface". An interface is the definition of methods and properties and object
has. In VFP, when we create an object, it just ends up with all the methods and
properties define (which is also true in .NET). With a specific interface
definition however, it is possible to set the expectations. This is very
important for code reuse and generic programming.
Let's examine a simple VFP example: Let's say I want to create a toolbar that
has a save button. Whenever that save button gets clicked, it will call the
Save() method on the active form. This means that I expect that the form has a
Save() method. In other words: I expect that the form "implements an interface
that has the Save() method". In VFP, I can call the Save() method in the
following fashion:
_Screen.ActiveForm.Save()
In VFP, we do not have to specify that the active form implements a certain
interface. If a form has a Save() method, it will get called. This makes things
very easy, but it also has a significant downside: If the currently active form
does not happen to have a Save() method (perhaps because the developer forgot to
add it), then the system will crash during runtime. There is no way for the
compiler to verify whether or not the ActiveForm object indeed has a
Save() method.
.NET's strong typing solves this problem. In .NET, object references always
are of a specific type (or class... which is really the same as a type in .NET).
So let's say we have a reference to the active form called "ActiveForm". (For
now, we do not care how that came to be). This reference would not just be typed
as an object (as in VFP), but it would be of type "System.Windows.Forms.Form".
In VB.NET, we could define this like so:
Dim ActiveForm As
System.Windows.Forms.Form
ActiveForm.Save() ' Fails!!!
However, the "Form" class does not have a Save() method. Therefore, the
compiler would not let us get away with defining a reference as "Form" and call
a Save() method. Instead, we would have to create a special form class that does
have a Save() method:
Public Class SaveForm
Inherits
System.Windows.Forms.Form
Public Function Save() As
Boolean
' Save and return success code in subclasses
Return
False
End Function
End Class
Now, we can use this form to define the type of our variable, and are thus
able to call the Save() method on it, since the compiler can verify that every
object reference that can be stored in this variable must have a Save()
method:
Dim ActiveForm As
SaveForm
ActiveForm.Save() '
Works
So far so good. The problem with this is that inheritance is a very static
technique. Just like in VFP, we can only subclass any class from one parent
class in .NET. And in this scenario, that means we already maxed out our
options. So if we have some cool class that we wanted to use as the parent
class, then we can't, because we always need to inherit from "SaveForm". Once
again, in VFP that is not a problem, because we could just add a Save() method
to our new form. But then again, that would lead to a quality problem that we
really wouldn't be happy with in modern development. And that's where Interfaces
come into play!
Interfaces allow us to define that a certain object has certain methods,
without wasting an inheritance relationship. So instead of creating a class, we
can define an interface:
Public Interface
ISaveForm
Function Save()
As Boolean
End
Class
We can now create a form class that subclasses from whatever other form class
we want it to subclass, and in addition, we always implement the interface we
just defined:
Public Class MyCoolForm
Inherits SomeOtherForm
Implements
ISaveForm
Public
Function Save() As
Boolean
' Save here...
Return True
End
Function
End Class
When we now talk to an object created from this class, we can look at it in
several different ways: We can look at it as a generic form (without a Save()
method), or we can look at it as type "MyCoolForm" (which has a Save() method,
but our toolbar button would not know that since it has no concept of what
"MyCoolForm" is), or as type "ISaveForm" (which has only a Save() method and the
toolbar button can use it). So we can do this:
Dim ActiveForm As
ISaveForm ' Uses the
Interface!!!
ActiveForm.Save() '
Works, no matter what it is subclassed from
The nice thing is that this is all very easy to do (once you get comfortable
with this new concept), and it is very easy to quality control, since the
compiler can verify 100%, that our code will work at runtime.
Posted @ 2:07 PM by Egger, Markus (megger@eps-software.com) - Comments
Friday, July 01, 2005
Care to Enumerate?
Have you checked out .NET enums yet? No? Well, you should!
Enums are a handy way to define a limited list of setting. Let's say you have
an object that stores different types of phone numbers. How do you define the
types? Perhaps you have a PhoneType property, which might be a numeric
type. 1 stands for office phone, 2 for home phone, 3 for cell phone, and so
forth. The problem with this approach is that it is hard to memorize all these
settings. As a result, it is easy to get the mixed up, or assign a value that is
invalid altogether. In .NET, there is a very simply solution to that particular
problem: You define an "Enum". This can be done like so:
public enum
PhoneTypes
{
Office = 1,
Home =
2,
Cell = 3
}
This defines a new type/object (remember that there is no difference in .NET
between a data type and an object) which derrives from the .NET internal type
"Enum". This is the fancy way of saying "we created something that is an object
and it behaves according to the rules of enums". This means, we can now use this
enum in our code:
PhoneTypes myType;
This defines a local variable called "myType", and it is of type
"PhoneTypes". This would be kind of like the following statement in VFP
(assuming this was available in VFP, which it isn't):
LOCAL myType AS PhoneTypes
We can of course also assign a value to our variable:
myType = PhoneTypes.Cell;
What's cool is that we do not have to remember that a 3 indicates a cell
phone. We just say "PhoneTypes.Cell" as we defined it in the enum. Also, we do
not even have to remember which PhoneTypes are available, because as soon as we
type "PhoneTypes." C# IntelliSense provides a list of all available PhoneTypes.
(Note: If you do this in VB.NET, the editor works even better by showing the
list of available phone types as soon as you type the "=" sign...). Note also,
that we have to pick one of the available options, if we pick something else,
then the compiler catches the problem and won't let us get away with it.
(Although there are some "expert tricks" to cheating that system, but we will
ignore those for now...).
Of course, we can also expose properties that take advantage of this new
type
public PhoneTypes
Type
{
get
{
return
wherever the value comes
from...
}
set
{
wherever we want to store the value to = value;
}
}
What's also very cool is that it is very easy to store the actual numeric
value to the database (or retrieve it from there). Let's say phoneRow
is an ADO.NET DataRow out of a DataTable in a
DataSet, and "iType" is an integer field in the underlying SQL Server
table. In this case, our property could be coded like this:
public PhoneTypes
Type
{
get
{
return (PhoneTypes)phoneRow["iType"];
}
set
{
phoneRow["iType"] =
(int)value;
}
}
In other words: We can simply cast any enum to an integer value, and
vice-versa. If our database field has the integer value 2, and we cast it to our
enum, it will end up as "PhoneTypes.Home". Pretty slick,
hm?
Posted @ 10:49 PM by Egger, Markus (megger@eps-software.com) - Comments