By Richard A. Schummer
Originally Published in the May 1998 issue of FoxTalk
Copyrighted 1998 - Richard A. Schummer
AddObject is a method that can be called by each of the VFP container objects to add other objects to them. This month Rick explores how to use this powerful method, demonstrates how it is done with some examples, and offers some further suggestions where developers can use it.
Developers that have worked with Visual FoxPro have come to understand that there is usually more than one way to accomplish just about every task within the VFP development environment or within an application. There are a number of ways to add objects to a container object. Most developers I have talked to add objects to these classes at design time using the Class and Form Designer. Some developers prefer to write classes in program code. This article will focus, in detail, the programmatic way to add objects via the AddObject method at runtime and discuss some of the benefits and drawbacks.
Visual FoxPro has several container classes available for developers to leverage. These classes are called container classes because they can contain other objects.
Table 1. List of the VFP Container Classes
There are a number of ways we can add an object to a object container visually:
Each container class has an AddObject method. The native VFP behavior of this method is to take the requested object and perform an implicit CREATEOBJECT within the container object. Developers can add code to the AddObject method. This code is executed when you programmatically add a new object, but before the new object is instantiated via the implicit CREATEOBJECT. The syntax for the AddObject method is as follows:
Object.AddObject(cName, cClass [, cOLEClass] [, aInit1, aInit2 ...])
The cName parameter is the name assigned to the object when it is instantiated. This is the same as the Name property you enter in the Property Sheet when using the visual designers. The cClass parameter is the class name of the object as it is stored in a Visual Class Library, defined in code in a program or procedure file, or in a compiled app/exe file. The cOLEClass is the name of a registered OLE class. Any other parameters (aInit1, etc.) are parameters that are passed along to the Init method of the newly added object. One key point to remember when using this method is that the added object is invisible. You need to explicitly set the Visible property to true for this object if it needs to be seen by the end user. Here is a basic example that can be added to the Init method of a Form class to add a label object and place it where it belongs on a form.
.Caption = "Company:"
.Alignment = 1
.Top = THIS.txtCompany.Top + 5
.Left = THIS.txtCompany.Left - ;
THIS.lblCompany.Width - 3
.Visible = .T.
This method gives developers an opportunity to control or override whether the object gets instantiated at runtime. You accomplish this by placing logic in the method that determines if the object should be instantiated, and issue a NODEFAULT if you don’t want the object created.
LPARAMETERS cName, cClass
IF cClass = "txtProfitMargin "
IF "CustRep " $ goApp.oUser.mSecurity
The AddObject method can come in handy to save memory and Window’s resources. It also can be a way to control what the end user sees or has access to within any container. Setting the container’s Visible property to false only makes it invisible to the user, but the object still resides in memory and takes up resources.
There are some major drawbacks to this method of adding objects to a container instead of using the visual designers. These drawbacks are not reasons to avoid the AddObject, just things developers need to be aware of when using this method. The first one is the biggest disadvantage. If you do issue a NODEFAULT in the AddObject method, you have to start checking for object references using TYPE() and ISNULL() any where you use this object in code. VFP does not catch these type of errors at compile time. If your testing is not complete you might be getting support calls with “Object does not exist errors”. The next disadvantage is that the object will not appear in the VFP Editor Object List that is available when editing method code. The last drawback is that the object will not be immediately obvious to other developers on your team where the object came from since it is not visible when looking in the designer.
Headers in a Grid
How many times have you wished to customize the code in the Header object? Never, all the time? I wanted to be able to sort the data in a Grid based on click the Header. Each time I wanted the feature I was customizing each Click method in each Header that I wanted to sort on. This becomes a pain if you have several dozen Grids within an application. The following code is a custom class that I have created to get around the limitation. This class must sit in a program since the Header object cannot be created in the Class Designer. To use this class the following code must be executed before the class can be instantiated:
SET PROCEDURE TO Header.prg ADDITIVE
* Header .prg
DEFINE CLASS PSHeader AS Header
cOrder = ""
nOrderColor = RGB(255,0,0)
nNotOrderColor = 0
LPARAMETERS tcOldCaption, tnOldBackColor
THIS.Caption = tcOldCaption
THIS.BackColor = tnOldBackColor
THIS.nNotOrderColor = tnOldBackColor
lcOrder = THIS.cOrder
SET ORDER TO (lcOrder)
THIS.BackColor = THIS.nOrderColor
* Leave as is
*: EOF :*
I have created a sample form called frmHeader.scx (included in this month’s download files from www.pinpub.com) with a Grid that is based on the VFP sample data customer table. The following code resides in the Init method of a Grid Object.
* Replace default headers with custom one
FOR EACH loColumn IN THIS.Columns
* Save default caption and color
lcOldCaption = loColumn.Header1.Caption
lnOldBackcolor = loColumn.Header1.BackColor
* Name Header Obj after ControlSource
lcControlSrc = loColumn.ControlSource
lnControlSrc = RAT(".", lcControlSrc) + 1
lcControlSrc = SUBSTR(lcControlSrc, lnControlSrc)
* Remove old and create new header
* Set columns with sort feature
THIS.Column1.grhCust_id.cOrder = "cust_id"
THIS.Column2.grhCompany.cOrder = "company"
THIS.Column3.grhContact.cOrder = "contact"
*Set the initial order
The FOR EACH…ENDFOR code can reside in your custom Grid baseclass and just place a DODEFAULT() along with setting the cOrder property code in the Init. You can also build logic into the Header class to check for index tags automatically and set them as you add the new Headers.
The same concepts can be applied to adding Columns in a Grid, Cursors in DataEnvironment, Pages in a PageFrame, Option Buttons in an Option Group, and so on. This is a great way to customize and extend the behavior of the VFP classes that cannot be subclassed in the Class Designer.
Builders can add objects to a class during development time. This is an excellent way to extend the objects. One case might be the sample I designed for replacing the Header objects in a Grid. This can be done at design-time instead of run-time by implementing a Grid builder that replaces the Header objects.
AddObject can be used to delay the instantiation of objects until they are needed. One example is used often by developers using VFP 3.0 which is slower to instantiate objects. You can easily add objects that sit on a Page in a PageFrame. First you create a Container of the interface objects that will be displayed on a Page. In the Activate method if the Page you check to see if the container object exists, if it does not you can call AddObject to add it. This adds a container level to the object hierarchy and adds another class to be modified during development, but it gives your users a faster form instantiation. If the user rarely selects the Page, then they get full benefit of the speedier form and less memory resource usage. If they use the Page all the time they will get a faster form start up, but slower Page selection which may not be ideal.
AddObject can be used to customizing objects based on user security or site implementation. This is based on the Strategy Design Pattern. You may design different classes for different types of users. Maybe the Manager gets a view of the entire customer list with a some CommandButtons that perform managerial functions and the Clerk gets clerical functionality via different CommandButtons. Add the appropriate CommandButtons at runtime based on the user’s security level. If the application is used in different countries and have different taxes you can build a variety of tax calculation classes that can be implemented based on the location of the system. This implementation can be based on the Strategy Design Pattern. Steven Black discussed the Strategy Design Pattern (OOP:Design Pattern: Add Design Flexibility with a Strategy Pattern January 1997) that implements this for discount and tax calculations.
Class definition "name" is not found (Error 1733)
This error occurs when the class definition is not accessible. Either the class does not exist or the class library is not listed in SET CLASSLIB TO or the SET PROCEDURE TO if it is defined in code. You need to add the location of the class to the “path” via a SET CLASSLIB TO or SET PROCEDURE TO depending on where the class resides.
Object class is invalid for this container (Error 1744)
This error is triggered when the object being added to the container is invalid as a member of the parent container. For instance, you cannot have a Page object be a member of a Control object or a Grid Column be a member of a PageFrame.
Property "name" is not found (Error 1734)
This error is generated when the AddObject is executed for non-container objects. This error may seem obvious, but you cannot execute AddObject for TextBoxes or other non-container objects.
The RemoveObject method provides the exact opposite affect, it removes the selected object which must already exist within the container. The developer’s custom code included in the method is executed before the object is removed from the container, just like the AddObject custom code.
This powerful control over object instantiation is an important addition to a Visual FoxPro Developer’s toolbox. There are a number of situations where it can be implemented which I have outlined using only a few examples. Hopefully this will inspire you to find place where it can fit into your developer toolbox.
Sidebar - Adding ActiveX Controls on the Fly
The integration of ActiveX controls with Visual FoxPro has opened a new world of component development to FoxPro developers. These powerful development components add a bit of complexity at the same time, especially if the ActiveX controls are upgraded. The following scenario demonstrates the issues:
What happened? “OLE class not registered error” is triggered. ActiveX controls are not always backward compatible. Information about the control from the development machine is stored in the form/class in the OLE and OLE2 fields of the metadata file (SCX or VCX). This information is used during the OLE object’s instantiation. If the information is not the same as the control registered on the production machine the error is triggered.
You can install the upgraded control, but this can negatively impact other applications loaded on the system that use these controls. The better option might be to use AddObject to dynamically add the control at runtime. This way the Class ID (CLSID) is not stored in the form or class and is added with no problem.
The drawback is that the ActiveX control will have no customized code and you cannot add code on the fly. Depending on the control, you may be able to build another custom object that can contain the code needed at runtime. You can call this class to perform the needed actions or make calls to the generic methods in the ActiveX control. This may not work in all cases depending on the design and purpose of the ActiveX control. Here is a scenario:
At runtime you can add a TreeView control dynamically by placing code like this in the forms Init method:
The custom tree handler object can have methods to initialize the TreeView, position it on the form, connect an ImageList for icons, add nodes, delete nodes, refresh it, etc. These methods can be called from the custom handler’s Init method or other objects on the form depending on the requirements. Then when the developer updates the development environment or the client updates the production machines (which never happens <g>) with new controls the forms continue to work.
The code cannot be added to respond to the events of the control which is a huge drawback. The code can be added at design time through a builder via WriteMethod. This is nice if you have built a third-party product that uses ActiveX controls. Distribute the changes, run the ActiveX builder/updater program and have the developer rebuild the app. Stonefield Database Toolkit uses this concept when updating from one version to another.
One note of caution, this methodology does not help if the company that creates the control modifies the public interface of the control. For instance, the manufacturer changes the name of the method used to update the tree nodes or takes away the property that connects the control to the ImageList control for displaying icons. This is behavior not typically found, but definitely has bitten developers in the past.
This site is designed to be viewed in 800x600 video resolution or higher, but it should work fairly well at any resolution and with any of the major browsers (all free!). Optimized for MS Internet Explorer, Firefox, and Opera (mostly works with Mozilla and Netscape Navigator).
Copyrighted 1998-2005 Richard A. Schummer