Wiki Home

Refresh Workshop


Namespace: WIN_COM_API
REFRESHING WORKSHOP

This thread intended to foster a broader discussion of Refresh issues within the VFE framework. Please join in with your thoughts and ideas.

First off, let me say that F 1 Technologies has done a great job with VFE and overall I'm very pleased with it. The following is not intended to be a disparagement, but more of a "stream of consciousness" to try and get at the root of this issue.

That issue is -- VFE does a lot of Refresh()ing. For whatever reason, it does. In looking at the code, it's my suspicion that the VFE developers wanted to make extra, extra sure that all the controls got updated correctly -- I think because it's possible that people were misusing the framework. Perhaps people were putting controls in the form layer as opposed to the presentation object layer. Perhaps people weren't using VFE's controls but VFP's base controls. Perhaps VFP was buggier in prior versions. Perhaps various OLE controls were a bear to deal with -- who knows.

You can see this in a number of ways. The simplest is to put a DEBUGOUT statement in an object's Refresh method (especially child object) and watch the Debug Output window as one navigates through the form. Initialization is a particularly Refresh() happy area but I don't have a solution for that as of yet. I do have a partial solution for when one navigates around.

Let's walk through the code within a typical refresh situation -- What happens when the Next button is selected from the menu?

Eventually, we work our way down to the cBizObj.Navigate_Perform() method. There, the call is made to the cCursor.MoveNext() method and a token is returned, essentially indicating if the record pointer actually moved. Regardless of success, the cBizObj.RequeryChildren() method is run. Offhand, I can't even tell where the Requery() is done for the main cursor (I thought it was part of the navigation in cCursor but I'm mistaken) but the children, if any, already are set up and ready to go.

We eventually climb back out to the cPresentationObj.Navigate(). If all is good, we run the cPresentationObj.RefreshUI(). Now this gets interesting.

NOTE: If someone knows how to format source code in the Wiki, please feel free to do so. Thanks, Bill
Done; use < pre >.

The parts pertaining to refreshing are shown below.
==============================================================================
* Method:			RefreshUI
*** cut stuff here
* Modifications:
* 08/09/1999		Made sure the refresh event of the presentation object
*					was called whenever refreshUI is called. This ensures
*					that contained controls are refreshed.
*** cut stuff here
* 04/25/2000		Added code to refresh the list object if there is one 
*					registered with the presentation object.
* 11/20/2000		Passed the tlForce parameter to the RefreshChildren
*					method.
*==============================================================================
LPARAMETERS ;
	tnPosition, ;
	tlForce

LOCAL ;
	llLockScreen, ;
	llAllowEdit

WITH Thisform
	llLockScreen = .LockScreen
	.LockScreen = .T.

	IF .Visible OR tlForce

		WITH This	
*** cut code here

			IF Thisform.oPresentObj = This
			
				ThisForm.RefreshForm()
				
			ELSE
			
				IF ;
					NOT .Class == Thisform.oPresentObj.Class OR ;
					NOT .ClassLibrary == Thisform.oPresentObj.ClassLibrary
					
					.Refresh()	
				ENDIF
					
			ENDIF

			.RefreshChildren(FILE_BOF, tlForce)
			.RefreshUI_Post(tnPosition)
			.SelectCursor()
		ENDWITH

	ENDIF

	.LockScreen = llLockScreen
ENDWITH

I believe in essence what is happening is the following: If this presentation object is the main presentation object (indicated by Thisform.oPresentObj = This), then go to the form level refresh. For other presentation objects, refresh thyself. Regardless, refresh my children, if any exist (This is where the duplication resides but just make a note of it for now).

Side note -- take a look at the 8/9 note to this class. From what I see, the Refresh() event for the presentation object is NOT called if it is the main presentation object. I believe that note is just wrong.

What happens in the form's RefreshForm? Let's look...
*==============================================================================
* Method:			RefreshForm
*** cut stuff here
*==============================================================================
LOCAL ;
	llLockScreen

WITH This
	IF NOT .lRefreshInProgress
		.lRefreshInProgress = .T.
		llLockScreen = .LockScreen
		.LockScreen = .T.
		.Refresh()
		.RefreshToolbar()
		.LockScreen = llLockScreen
	ENDIF
	
	.lRefreshInProgress = .F.
			
ENDWITH


In essence, the whole form is refreshed, as you'd expect.

If one has a simple screen (no pageframes, no child presentation objects), the process is nearly over. Going back to the PresentationObj.RefreshUI(), the RefreshChildren method is run but there isn't any children so there's no harm done and we drop out of the RefreshUI() and continue on for a few more statements.

What if there's children? Therein lies the rub...

First, let's define a child presentation object. To define that, we must first define child business objects. A child business object is one whose parent is the main business object. These are defined by the cParentBizObjName property in the business object. More importantly, the child business object has an .oParentBizObj reference to the main business object. If this is true, the object that hosts the child business object is considered a child presentation object, by my definition.

Let's take a look at the cPresentationObj.RefreshChildren method.
*==============================================================================
* Method:			RefreshChildren
*** cut stuff here
*==============================================================================
LPARAMETERS ;
	tnPosition, ;
	tlForce
	
LOCAL ;
	loBizObj
	
WITH This.oBizObj
	IF .HasChildren()
		FOR EACH loBizObj IN .oBizObjs.Item
			IF ;
				VARTYPE(loBizObj) = T_OBJECT AND ;
				VARTYPE(loBizObj.oHost) = T_OBJECT AND ;
				loBizObj.oHost.lDisplayed
				
				IF PCOUNT() >= 1
					loBizObj.oHost.RefreshUI(tnPosition, tlForce)
				ELSE
					loBizObj.oHost.RefreshUI(FILE_BOF)
				ENDIF						
				
			ENDIF
		NEXT
	ENDIF
ENDWITH

In essence, the RefreshUI is called for each child presentation object and we've seen the code for that routine. Let's go back for a moment to the RefreshUI():
			IF Thisform.oPresentObj = This
			
				ThisForm.RefreshForm()
				
			ELSE
			
				IF ;
					NOT .Class == Thisform.oPresentObj.Class OR ;
					NOT .ClassLibrary == Thisform.oPresentObj.ClassLibrary
					
					.Refresh()	
				ENDIF
					
			ENDIF

If the presentation object is not the main presentation object, the Refresh() is run. (I don't know what the IF test is preventing but that's for another discussion). However, the form's RefreshForm did a .Refresh() on the form, which forces a Refresh() on all contained objects. Ergo, the .Refresh() method is run at least twice for all child presentation objects. On top of that, the situation is even worse for all contained objects of that child presentation object.

This is where some of the slowdown is occurring. The problem multiplies with the more complexity one has on the form. Imagine a 10 page pageframe with children and grandchildren presentation objects -- bleah!

I believe I have a mechanism to suppress some of this extraneous refreshing. Again, this only occurs with child presentation objects and their contained controls. This mechanism is really one giant kludge, so don't expect this to be incorporated into the framework. The developers
and you the reader are welcome to use it if they wish but I suspect they won't.

The kludge is as follows: Note the cForm.RefreshForm() -- there's a variable set called .lRefreshInProgress. When that is true, ignore the refreshes and let the RefreshChildren() call in the cPresentationObj.RefreshUI() method handle it.

So here goes....

Note that this solution assumes version 6.3 of VFE.

In your presentation object subclass (iPresentationObj or below), add the following:

Property Value Description
lExtraRefreshing .f. property used to determine if extra refreshing is occuring.

Method
lExtraRefreshing_access method used to determine if extra refreshing is occuring.

In the lExtraRefreshing_Access method, add the following:

--
WITH THIS

  .lExtraRefreshing = VARTYPE(THISFORM) == T_OBJECT ;
  AND THISFORM.lRefreshInProgress AND THISFORM.oPresentObj # THIS

  RETURN .lExtraRefreshing

ENDWITH  
--

Refresh

In the Refresh method, add the following:

--
** Superfluous refreshing test
IF THIS.lExtraRefreshing

  NODEFAULT
  RETURN

ELSE && not superfluous, so run the default...

  DODEFAULT()

ENDIF THIS.lExtraRefreshing
** End superfluous refreshing test

--

For every contained control within the child presentation object that has a native Refresh() method, add the following:

Property Value Description
lExtraRefreshingAwareHost .f. property used to determine if the host of
this control is aware of extra refreshing.

oController .NULL. object property used to hold the controller for this object.

Method
oController_Access Access method used to get an object reference to the
controller of this object.

In the oController_Access method, add the following:
(NOTE: The following assumes that iPresentationObj is the name of the subclass where you're putting the presObj code. Please adjust for your subclass name!!!)

--
WITH THIS

  ** Have a controller test
  IF ISNULL(.oController) OR VARTYPE(.oController) # T_OBJECT

    LOCAL loController

    loController = .oGlobalHook.GetController(THIS)

    ** Have the controller test
    IF NOT ISNULL(loController)

      .oController = loController

      ** Now we have the controller (host).
      ** Is the host aware of extra refreshing?
      .lExtraRefreshingAwareHost = ;
      IsA(loController, [iPresentationObj]) ;
      AND PEMSTATUS(loController, [lExtraRefreshing], 5)

    ENDIF NOT ISNULL(loController)
    ** End have the controller test

    loController = NULL

  ENDIF ISNULL(.oController) OR VARTYPE(.oController) # T_OBJECT
  ** End have a controller test

  RETURN .oController

ENDWITH

--

In the Destroy, add the following:

--
THIS.oController = NULL

DODEFAULT()

--

And in the Refresh, add the following:

--
WITH THIS

  LOCAL llSuppressRefreshing, loController

  llSuppressRefreshing = .f.
  loController = .oController

  ** Have a refreshing aware host 
  ** and we're doing extra refreshing test
  IF NOT ISNULL(loController) ;
  AND .lExtraRefreshingAwareHost ;
  AND loController.lExtraRefreshing

    llSuppressRefreshing = .t.

  ENDIF NOT ISNULL(loController) ;
  AND .lExtraRefreshingAwareHost ;
  AND loController.lExtraRefreshing
  ** End have a refreshing aware host 
  ** and we're doing extra refreshing test

  loController = NULL

  ** Suppress refreshing test
  IF llSuppressRefreshing

    NODEFAULT
    RETURN

  ELSE

    DODEFAULT()

  ENDIF llSuppressRefreshing
  ** End suppress refreshing test

ENDWITH

--

Whew! That's a lot of work!

I have tried the above on a couple of situations and haven't seen any side effects, only performance increases when I'm using child presentation objects. I haven't tested on a pageframed form yet. I'm hoping someone out there will.

Try it out and let me know how this works for you.

Bill Anderson
Bill, I did a little bit of investigating on this and discovered a few things and also have a few things to point out.

1. A child presentation object does not have to be contained in the same form as its parent, so the call to RefreshChildren is necessary to make sure that child presentation objects are refreshed in child forms.

Can't a check be added to not call refresh() in a presobj if it is contained in a presobj? Of course, drilling up may be as expensive as the refresh in the first place. Does RefreshChildren actually refresh presobjs that are on other forms? You don't actually relate presobjs? -- Bob Archer

2. There's a DODEFAULT() in cPresentObj.Refresh(), which is unnecessary. At one point in time the mere precense of a DODEFAULT() in a container caused VFP to run its refresh code twice. That no longer appears to be the case with SP5, but eliminating this call does improve performance slightly.

3. Adding IF NOT .lRefreshInProgress AND NOT tlForce to cPresentationObj.RefreshUI just after the WITH and ending it just before the ENDWITH makes a slight difference as well.

4. At some point in time we are going to add the oController property and access method to all of the UI controls and change all of the related code accordingly, but that's going to be a fairly big job. I think the INIT of cPresentationObj can also just do a This.SetAll("oController", This) or something similar to reduce the hoops the access method would have to jump through.
Mike Feltman

Contributors: Bill Anderson
Category Visual FoxExpress Refresh Workshop Category Visual FoxExpress
( Topic last updated: 2003.12.09 12:57:32 PM )