Design Time References (Outermost Container)

Version: 1.02.00 - last update: Sunday, September 27, 2009, 13:25:00

Previous EntryTips & Tricks HomeNext Entry 


All you have to know about finding the topmost containing parent reference in a VFP designer session.

Intro

I like to work with VFPX tools like the PEM Editor. This morning I downloaded the latest version 4.10 (August 16, 2009). The update fixes some minor bugs and adds some new functionality. Just go here and get your own copy. Among other things, the update promises to fix a bug that occurred when working with VFP FORMSETs. Well, it kept it’s promise … almost :-) With PEM Editor 4.10 now you can edit PEMs of a formset class. Alas, Version 4.10 still fails when working with a form-based (SCX-based) formset! Well, one could argue that this isn’t such a big issue – right you are – that’s no real show-stopper ;-)  But it reminds me of an old project I was working on years ago. I named it the “Sedna Class Tree” project those days.
My “Sedna Class Tree” project started with an initial posting of Carl Carstens on fox.wikis.com which I downloaded to play with. I finally posted a “pimped-up” version which you still can find here. The code which could be call a “pre-alpha” version, never got more than a starting point :-)
One of the first hurdles to jump was getting the right references of the class instances inside VFP’s class- and form designers. This seems easy to achieve on first sight. But, as you can see it isn’t, using the example of the PEM Editor project!

What makes the task so difficult

VFP has two different visual designers which are in the focus of our interest: the form- and the class designer. Both running within VFP’s desktop as long as we do not use the command line syntax “MODI FORM …” to alter that behaviour.

 DesignTimeRefs001 DesignTimeRefs002

The screenshots above show the sole outer designer windows without their contained objects. I moved them out of sight manually before taking the snapshots. There are two things we have to know about VFP’s visual designer windows:

1st – they are “real” form instances that can be accessed by us like any other form we create using something like: loF = CREATEEOBJECT(“FORM”)

2nd – they are not only forms, but they are FORMSETs (having one form only).

So far so good – now, tell me how do you find out the difference between the following two Form Designer instances:
Instance #1 is a Form Designer session that represents a single FORM.
Instance #2 is a Form Designer session that represents a FORMSET containing a single FORM.

Because designer sessions are real VFP formsets the second one cannot be a formset containing a formset containing a form! However, the first one is a regular formset containing a form. And the second one? There’s the rub: the second one also is a regular formset containing a form! How can we distinguish between the two reliably?

How to retrieve Designer references

There are no direct references to designer instances available. We have to use VFP’s ASELOBJ() function instead. ASELOBJ(aName, 1) will find the parent container of any selected/active object in the designer session. Since VFP 8 ASELOBJ() is enhanced – you can use ASELOBJ(aName, 3) to get a three-element array containing information in context with the currently designer window. That’s what we need! It is also good to know that even ASELOBJ(aName, 1) returns the form reference directly in our scenario (even if there’s no control on the form). Next, we have to get the form’s parent, which then is (in case #1) the reference to our designer’s formset. Doing the same within the second scenario brings us to a formset reference, too. But, the latter is not the designer’s formset, but the formset we are going to create within the designer right now.

La Grande Complication (Part I)

Let’s have a look on a second scenario running three Class-Designer sessions:
Instance #1 is a Class-Designer session that represents, let’s say, a CONTAINER (with or without embedded objects).
Instance #2 is a Class-Designer session that represents a single FORM (again, with or without embedded objects).
Instance #3 is a Class-Designer session that represents a FORMSET (containing a single form).

Using ASELOBJ(aName, 3) as described above, we will find out that our CONTAINER object is hosted inside another form (our container’s PARENT) which looks like the following screenshot (I removed the surrounding Class Designer window before taking the snapshot):

DesignTimeRefs003

Let’s check if we can touch this form instance surrounding our container class. Type into your command window:
ON KEY LABEL F11 ? ASELOBJ(aSelObjects, 3)
Next bring your Class Designer form on top, press F11 and then type:
aSelObjects[1].PARENT.Borderstyle = 1
You should get something like this:

DesignTimeRefs004

As we can see, there is another internal form hosting our controls, always! With the help of this hosting form VFP can show us the title/caption of our control using the host-form’s caption. But, what if our control is a form itself – or a toolbar? Can you see the dilemma? In that case the control’s host-form inside the Class Designer is the control itself. We have to take care of that later…

La Grande Complication (Part II)

The same is true if we are editing a formset in our Class Designer session. Suddenly, the Class Designer formset no longer is the surrounding Designer formset, but the concrete formset we want to save and use in our app later on.

La Grande Complication (Part III)

Finally there is a Form Designer related speciality. In contrast to VFP’s Class Designer the Form Designer allows us to create and modify a new form without having it saved. The Form Designer creates a temporary SCX file in this case. You can check that out using ASELOBJ(aName,3) which will return a three element array. Element #2 holds the full path to the SCX. AS long you don’t have saved your new form, the path points into VFP’s temp-directory. If you like to display the Form Designer’s caption inside your own app this is annoying, coz you have to create a workaround first. Otherwise you’ll end up showing some gibberish temp file name…

DesignTimeRefs005

… like PEM Editor 4.10 still does :-)

Some (good) things we can rely on

While monitoring a Class Designer instance we can be sure that the user/developer cannot change a FORM class within that session to a FORMSET class (or vice versa). The bad news is, this isn’t true within a Form Designer session! Monitoring a Form Designer session we have to be very careful when the Formset’s FormCount is down to one. In that case the user is able to remove the formset and we have to update our tree view display and more.

Every Designer session is made up of at least one formset containing one form. That’s the last good news :-)

How to crack nuts like these

I remember well that I spent many hours checking the internal appearance (object values) of all possible Form and Formset combinations. Finally, I was able to determine exactly what kind of visual editor session my ASELOBJ() function was pointing to. Here are my findings I like to share with you. I hope they will find their way into next PEM Editor’s update and other good tools that have to deal with VFP’s designer sessions.

Typically, our monitor runs timer-driven. Running in VFP 9.0 we should use Win-Event binding instead. But this is another story. Most of you who are interested in my findings already have successfully coded that part, don’t you? To keep things simple, we will utilize a hotkey like F12 to launch the demo code. Save the listing below to, let’s say, “Analyser.prg” somewhere in your VFP search path. Set your hotkey like this: ON KEY LABEL F12 DO Analyser.prg

Next, begin editing classes and forms. Create new forms on the fly, change them to a formset (and back to a form) and press F12 in between. You will notice after a while, that you cannot “cheat” my analyser – it always tells the truth ;-))

Have fun!

*\\ ************************************************************
*\\
*\\ 		  VFP Root-Object Analyser
*\\ 	     (C) 2009 by Burkhard Stiller
*\\
*\\ Raise this program using a hotkey like ON KEY LABEL F12
*\\ It will echo to VFP's desktop what kind of object is
*\\ the top level container in the current Form- or Class-
*\\ Designer session (the one which is WONTOP())
*\\
*\\ ************************************************************
*//
LOCAL lnClassContextCount	AS INTEGER	,;
      lcDesignerType		AS STRING	,;
      lcDesignerCaption		AS STRING	,;
      loFormRef			AS FORM		,;
      loFormSetRef		AS FORMSET	,;
      llNoCheck			AS Boolean	,;
      loOutermost		AS OBJECT
LOCAL ARRAY laClassContext[3], laForm[1]
*//
STORE 0 TO    lnClassContextCount
STORE "" TO   laClassContext[2], laClassContext[3],;
              lcDesignerType, lcDesignerCaption
STORE NULL TO laClassContext[1], laForm, loFormRef,;
              loFormSetRef, loOutermost
*// -------------------------------------------------------------
*\\ Get caption of WONTOP()
lcDesignerCaption = WONTOP()
*\\ Fill class info. Array holds three elements:
*\\ 1 Object reference to container object
*\\ 2 Full path and name of .scx or .vcx file
*\\ 3 Full path and name of #INCLUDE file if available
lnClassContextCount = ASELOBJ(laClassContext, 3)
*\\ Check if we have an object reference in aClassContext[1]
IF VARTYPE(laClassContext[1], .F.) == "O"
   *\\ check what kind of designer we have:
   *\\ "VCX" | "SCX" or "TMP" := unsaved Form Designer session
   lcDesignerType = UPPER(JUSTEXT(laClassContext[2]))
   *\\ If we are monitoring a Class Designer session...
   IF m.lcDesignerType == "VCX"
      *\\ ... we have to run another check in addition
IF NOT INLIST(laClassContext[1].BASECLASS, "Form", "Toolbar") *\\ This is any other VFP control but a FORM or TOOLBAR loFormRef = laClassContext[1].PARENT ELSE *\\ This is a FORM OR TOOLBAR loFormRef = laClassContext[1] ENDIF
ELSE *\\ We're monitoring a Form Designer session *\\ laClassContext[1] always holds a FORM ref loFormRef = laClassContext[1] ENDIF *\\ get object ref of outermost FORMSET loFormSetRef = loFormRef.PARENT *// *------------------------------------------------------------- *\\ Check (bullet-proof!) what “kind” the outermost formset is *\\ Either it is a RealFormSet, which will be saved later, or *\\ it is the DesignerFormSet, that is used inside every *\\ designer instance during development. IF m.lcDesignerType == "VCX" *\\ Class designer DO CASE *\\ We are editing any NON-FORM/NON-FORMSET class: CASE EMPTY(m.loFormSetRef.CLASSLIBRARY) AND ; EMPTY(m.loFormRef.CLASSLIBRARY) *\\ Designer-forms & formsets have an EMPTY *\\ ClassLibrary property!!!!! loOutermost = laClassContext[1] *// *\\ We are editing a REAL FORMSET class: CASE NOT EMPTY(m.loFormSetRef.CLASSLIBRARY) AND ; EMPTY(m.loFormRef.CLASSLIBRARY) *\\ REAL FORMSETs' child forms have an EMPTY *\\ ClassLibrary property!!!!! loOutermost = m.loFormSetRef *// *\\ We are editing a REAL FORM class: CASE EMPTY(m.loFormSetRef.CLASSLIBRARY) AND ; NOT EMPTY(m.loFormRef.CLASSLIBRARY) *\\ Designer-formsets have an EMPTY *\\ ClassLibrary property!!!!! loOutermost = m.loFormRef *// OTHERWISE *\\ error! loOutermost = NULL *// ENDCASE ELSE (FORM designer) *\\ Get native properties of the FORM reference = AMEMBERS(laForm, m.loFormRef, 0, "N") *\\ Here comes the tricky part: Forms within *\\ Formsets do NOT have a BufferMode property!!! IF ASCAN(laForm, "BUFFERMODE", 1, -1, 1, 2+4+8) > 0 *\\ this is a REAL FORM (no Formset) loOutermost = m.loFormRef ELSE *\\ This is a REAL FORMSET (with one or more Forms) loOutermost = m.loFormSetRef ENDIF ENDIF *------------------------------------------------------------- *// ELSE *\\ error loOutermost = NULL *// ENDIF *// IF NOT ISNULL(m.loOutermost) *\\ ECHO to VFP's desktop
*\\ (
just comment lines out if you do not need them) ACTIVATE SCREEN CLEAR ? "Outermost Object in " + m.lcDesignerCaption + " is named: '" ?? m.loOutermost.NAME + "'" ? "BaseClass = '" + m.loOutermost.BASECLASS + "'" ? "Class = '" + m.loOutermost.CLASS + "'" ? "StoragePath='" + m.laClassContext[2] + "'" ? "IncludeFile='" + m.laClassContext[3] + "'" *// ENDIF *// *\\ Return object reference of RealOutermostObjectReference RETURN m.loOutermost

Previous EntryTips & Tricks HomeNext Entry