Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Determining class bases - or how to fake an interface identity with VFP


:P
On this page:

I’m in the midst of working through an update to Web Connection 5.0 for Visual FoxPro and one of the things I’m working on is an extended page framework that provides a control based architecture that’s similar to ASP.NET but using Fox code.

 

In the process a lot of objects are created and some objects get attached to others via some level of containership.

 

One thing that’s sorely missing in the FoxPro language is support for implementing an interface natively. Yes you can implement COM interfaces (of existing interfaces only though), but that’s not serving the same purpose as a native interface. Interfaces are useful to tell another class whether an object provides specific functionality and in most languages that support Interfaces it makes it easy to check for type membership so you can tell which type of object you are dealing with. In addition, interfaces provide a generic reference that can be used instead of a complete full reference to access an object reference genericly rather than specifically.

 

So, in my framework I use containership a lot so you see a lot of stuff like this:

 

THIS.lstCustomers = CREATEOBJECT("webdropdownlist",THIS)

THIS.lstCustomers.Id = "lstCustomers"

THIS.lstCustomers.Width = 350

THIS.lstCustomers.AutoPostBack = .T.

THIS.lstCustomers.Controlsource = [this.Page.oGuest.oData.Pk]

THIS.lstCustomers.HookupEvent("change",THIS,"lstCustomers_Change")

THIS.AddControl(THIS.lstCustomers) && Add to Page Object

 

__lcHtml = []

__lcHtml = __lcHtml + [</td>]+CHR(13)+CHR(10)

__lcHtml = __lcHtml + [</tr>]+CHR(13)+CHR(10)

__lcHtml = __lcHtml + [ <tr>]+CHR(13)+CHR(10)

__lcHtml = __lcHtml + [<td align="right" class="blockheader" valign="top">]+CHR(13)+CHR(10)

__lcHtml = __lcHtml + [Name:</td>]+CHR(13)+CHR(10)

__lcHtml = __lcHtml + [                                                                   <td>]+CHR(13)+CHR(10)

__lcHtml = __lcHtml + []

_1LU08WEHI = CREATEOBJECT("WebLiteral",THIS)

_1LU08WEHI.Text = __lcHtml

THIS.AddControl(_1LU08WEHI)

 

 

THIS.txtName = CREATEOBJECT("webtextbox",THIS)

THIS.txtName.Id = "txtName"

THIS.txtName.Width = 350

THIS.txtName.Controlsource = [this.Page.oGuest.oData.Name]

THIS.AddControl(THIS.txtName)

 

You can see a bit more of how this works here:

 

http://www.west-wind.com/wconnect/webcontrols/guestbook.wcsx

 

by clicking on the Show PRG button and scrolling to the generated class a ways down.

 

In the code above each control adds itself to the container – above to the page, but it could also be a child container – and the rendering code then figures out the hierarchy and appropriately renders the child controls.

 

 

 

So far so good. For a few controls however, there are specific child controls or child members that get added. For example a listbox has listitems and a DataGrid has GridColumns and GridItems.

 

THIS.radWebTool = CREATEOBJECT("webradiobuttonlist",THIS)

THIS.radWebTool.Id = "radWebTool"

THIS.radWebTool.Width = 400

THIS.radWebTool.RepeatDirection = [Horizontal]

THIS.radWebTool.ControlSource = [this.Page.oGuest.oData.WebTools]

THIS.radWebTool.UserFieldName = [Web Tool Used]

THIS.radWebTool.visible = .F.

THIS.AddControl(THIS.radWebTool)

 

 

_1LU08WEIX = CREATEOBJECT("weblistitem",THIS)

_1LU08WEIX.Id = "_1LU08WEIX"

_1LU08WEIX.Text = [FoxPro]

_1LU08WEIX.Value = [FoxPro]

THIS.radWebTool.AddControl(_1LU08WEIX)

 

 

_1LU08WEJ6 = CREATEOBJECT("weblistitem",THIS)

_1LU08WEJ6.Id = "_1LU08WEJ6"

_1LU08WEJ6.Text = [ASP.NET]

_1LU08WEJ6.Value = [ASP.NET]

THIS.radWebTool.AddControl(_1LU08WEJ6)

 

 

_1LU08WEJ7 = CREATEOBJECT("weblistitem",THIS)

_1LU08WEJ7.Id = "_1LU08WEJ7"

_1LU08WEJ7.Text = [ASP Classic]

_1LU08WEJ7.Value = [ASP]

THIS.radWebTool.AddControl(_1LU08WEJ7)

 

This works Ok here, because you generally will only pass one type of control in there. But if you were to pass in say a Label control to a DropDownList you run into trouble with the rendering.

 

So what’s needed is a way for AddControl to determine what type of object is passed. You might think the BaseClass or ParentClass properties might help but in most cases that’s not adequate if you’re inheriting from a base type or implementing ontop of a base class. For example, if I have a WebListItem object and WebRadioListItem inherits from it, it’s difficult to determine that the current instance supports the WebListItem class signature. You can easily get first and last in the object hierarchy but not what’s in the middle.

 

Interfaces are perfect for this sort of thing, but alas, there’s no native way to implement interfaces in VFP. Even with COM there’s no easy way to check whether a specific interface is supported (you can use GETINTERFACE() ) other than creating an instance and firing away on the reference.

 

So how do you work around this? It does get important if methods accept multiple kinds of objects and you want to determine which type of object you are dealing with.

 

What I’ve done in the past is to rely on VFP’s easy type checking mechanism – you can use the TYPE() command to look for a specifc property on the class and if it exists you can be reasonably sure that it’s the right  kind of object.

 

IF TYPE("oRef.SomeUniqueProperty") = "C"

… It's the type we want

ENDIF

 

I suppose you can take this a step further and add properties to your classes that are explicitly designed for checking for type.

 

DEFINE CLASS DataGridColumn as WebControl

 

ColumnText = ""

ColumnType = ""

ColumnExpression = ""

 

*** Interface Identifier

IsDataGridColumn = .t.

ENDDEFINE

 

You can then check for this ‘interface’ with code like this:

 

************************************************************************

* WebDataGrid :: AddControl

****************************************

***  Function:

***    Assume:

***      Pass:

***    Return:

************************************************************************

FUNCTION AddControl(loCtl)

 

DO CASE

CASE TYPE("loCtl.IsDataGridColumn") = "L"

   this.Columns.Add(loCtl.ID,loCtl)

CASE TYPE("loCtl.IsDataGridItem") = "L"

   this.Items.Add(loCtl.Id,loCtl)

OTHERWISE

   DODEFAULT(loCtl)  && Add to ChildControls

ENDCASE

 

ENDFUNC

*  WebDataGrid :: AddControl

 

 

It’s possible there’s another way to do this. Looks like Sys(1272) is supposed to walk the object hierarchy, but according to the docs it only works at runtime (which is odd). I tried this out on a few control in the IDE and it just returns the immediate class of this instance. Even if this would work it’d probably be even slower than the code above as you’d first have to call this function then try to match your class by running through the list.

 

The above is not optimal as TYPE() is fairly slow (basically an EVAL + type retrieval of the result) , but it’s acceptable in this scenario.


The Voices of Reason


 

Ana María Bisbé York
November 25, 2005

# re: Determining class bases - or how to fake an interface identity with VFP

Thank you Rick !!

You can find the Spanish version of this article at (Puede encontrar la versión en Español de este artículo en: )

http://www.portalfox.com/modules.php?op=modload&name=Sections&file=index&req=viewarticle&artid=104

Regards / Saludos,

Ana
www.amby.net
www.PortalFox.com

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024