RAS
Home
Welcome to RickSchummer.com
web site hosted by GoDaddy Software

(Version 4.5.19 - This site updated 02-Apr-2005)
 

What is new?

Favorites

Site Map

About RAS



United We Stand!

Blog

VFP Tools

VFP Books

VFP Announcements

Articles

Presentations

GLGDW 2001 Pictures

DevCon 2000 Pictures

Set Up Your Dot Com


Site Policies

Curriculum Vitae
Resume

Words of Wisdom

Track Space Station

Campgrounds

US States Visited

Browser Details

Photography Index

Biscuit Photos

Rocketry 2001 Photos


Older Information

News

RAS.Commentary

Great Truths

Websites of the Month

What was new (v3.x)

What was new (v2.x)

What was new (v1.x)


Using AddObject

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.

Container Classes

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

Column

Command Group

Container

Control

DataEnvironment

Form

Form Set

Grid

OLE Container Control

Option Group

Page

PageFrame

Toolbar

There are a number of ways we can add an object to a object container visually:

  • Select an object on the Form Control toolbar and then click on the container object in the Class or Form Designer.

  • Select one or more fields in a table in the DataEnvironment and drag them into a container object.

  • Select one field at a time from a table in the Database Designer or Project Manager and drag them into a container object.

  • Dragging a class from the Project Manager or Class Browser into a container object.

  • Running a builder that is designed to add objects like the Grid Builder which adds columns, headers, and controls. This is actually done programmatically, but through a visual interface.

AddObject Syntax

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.

THIS.AddObject("lblCompany", "label")

 

WITH THIS.lblCompany

  .Caption   = "Company:"

  .Alignment = 1

  .Top       = THIS.txtCompany.Top + 5

  .Left      = THIS.txtCompany.Left - ;

               THIS.lblCompany.Width - 3

  .Visible   = .T.

ENDWITH

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

     NODEFAULT

  ENDIF

ENDIF

 

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

 

  PROCEDURE Init

  LPARAMETERS tcOldCaption, tnOldBackColor

    DODEFAULT()

     

    THIS.Caption = tcOldCaption

    THIS.BackColor = tnOldBackColor

    THIS.nNotOrderColor = tnOldBackColor

  ENDPROC

 

  PROCEDURE Click

    lcOrder = THIS.cOrder

     

    IF !EMPTY(lcOrder)

      SET ORDER TO (lcOrder)

        

      THIS.Parent.Parent.SetAll("BackColor", ;

                                THIS.nNotOrderColor, ;

                                "PSHeader")

                               

      THIS.BackColor = THIS.nOrderColor

      THIS.Parent.Parent.Refresh()

    ELSE

      * Leave as is

    ENDIF

  ENDPROC

ENDDEFINE

 

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

  WITH loColumn

    IF !EMPTY(loColumn.ControlSource)

      * 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

      .RemoveObject("Header1")

      .AddObject("grh"+lcControlSrc, ;

                 "PSHeader", ;

                 lcOldCaption, lnOldBackcolor)

    ENDIF

  ENDWITH

ENDFOR

 

* 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

THIS.Column2.grhCompany.Click()

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.

Other Uses

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.

Errors Happen!

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.

RemoveObject

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.

Conclusion

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:

  • Modify or create a new Form

  • Add an ActiveX Control

  • Save the form, compile, test, implement, customer very happy as usual

  • Upgrade the control, for example implement a Service Pack for Visual Studio (after all the new version must be better than the old version, tongue firmly planted in cheek).

  • New controls installed

  • Customer requests changes

  • Install application at customer site after the “upgrade”, but do not include updated controls.

  • Run the application, bring up new form for the customer

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.

Solution:

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:

THISFORM.AddObject("oleTreeView", ;

                   "oleControl", ;

                   "COMCTL.TreeCtrl.1")

THISFORM.AddObject("cusTreeHandler1", ,;

                   "cusTreeHandler")

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.

Problems:

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.

- RAS

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
All Rights Reserved Worldwide
This page was last updated on 02-Apr-2005