Logo

Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.

ZOrder

ZOrder is a method of controls that manipulates which control is on top of a stack of overlying controls, but, far more importantly (and far less documented!), also controls the firing order of key events for a set of controls on a form. The fact that there’s no equivalent property for finding out who’s on top makes this a pain in the neck to work with.

Usage

oControl.ZOrder( nWhichWay )

Parameter

Value

Meaning

nWhichWay

0

Bring the designated control to the top of the stack.

1

Push the designated control to the bottom of the stack.

As is badly explained in the Help file, ZOrder does actually maintain the visual order of controls that might partially overlay each other. It could be incorrectly postulated from the tone of the topic that there might be an equivalent method for the graphical elements drawn on a form with the Line, Box and Circle methods. Nope. As we explain in the reference sections for those elements, graphical elements drawn with these methods are completely passive. Although they appear to be on top of our controls when drawn, they are, in fact, just the last thing to be drawn on the form. As the controls that appear to be underneath them become active or refresh, the drawings are erased. Moving a control that has been drawn over shows that nothing remains of the drawing underneath the control.

ZOrder serves a second function, one that has bollixed us good on a few occasions. ZOrder not only controls the appearance of controls within a form, it also controls the firing order of those controls during key events such as Init, Refresh and Destroy. This can be a distinct and separate order from the TabIndex, which determines the order in which an operator moves from control to control. It is also different from the PageOrder property of Page frames, which determines the internal organization of pages within the page frame. ZOrder’ing a page to the front brings it to the top, but does not alter its order within the Page frame.

We suppose that, by necessity, there must be some logic to the firing order of controls. FoxPro must keep a list somewhere of all the controls it needs to work with, because it needs to go through that list and run them all. As explained in “A Gala Event,” controls fire Init events from the innermost level out—a text box contained within a column within a grid within a page frame fires first, followed by the column, grid and page frame, in that order. During the Destroy process, the opposite occurs—events fire inward, as if the form implodes. But what of controls on the same level? If three text boxes are placed on a form, which fires first?

Some of the answer can be found within the ZOrder method. Controls on the same level of a container—form, formset, page, whatever—fire in the order they were added to the form, unless they have been moved from that order. In design mode, the “Send to Back” and “Bring to Front” options manipulate this order. This order is saved, not as an explicit property of our controls, but rather is implied in the order in which controls are saved—the actual record order of the SCX or VCX. In a runtime environment, the ZOrder method provides the same ordering function. The frustrating thing is that we don’t ever get to directly determine where we are in this list, only push a control to the front or back.

Have some patience with our explanation and we hope all will be clear when we’re done. It’s really not that bad.

On a visually designed form, the controls’ Init events fire in the order they were added to a form. This is also the default TabIndex order, the order in which the operator will move through the form if he presses Tab to go from control to control. Fine. The TabIndex order can be rearranged by using the Tab Order option on the View menu pad or by directly changing the TabIndex property of each control. This does not change the ZOrder. On the other hand, using the “Send to Back” and “Bring to Front” options on the Format menu changes the ZOrder of the controls. A control sent to the front is the last to fire its Init, and the first to be destroyed. An element sent to the back acts as if it were the oldest element on the form, firing its Init first, and being the last control to be destroyed.

If the form is generated from code rather than an SCX, each Init fires in the order it's specified with ADD OBJECT in the form's definition, and is unaffected by ZOrder. Our third example demonstrates this. In a coded form, all the Inits still fire, regardless of whether you ZOrder a control to the top of the stack. In a visually designed form (an SCX), an Init that forces its control to the front (by issuing the command This.ZORDER(0)) prevents the other controls' Inits from running. Bear this in mind if you mess with ZOrder and try to convert a form from visual to hand-coded.

While a form is up and running, a control can be programmatically sent to front by calling the control’s ZOrder(0), or to back with ZOrder(1). This has a crucial message for us: Don’t mess with the ZOrder while program execution is within one of those events that fires in ZOrder order—Init, Destroy, Refresh and perhaps others—without a great deal of care. As shown in the example below, it’s relatively easy to get stuck in a loop with two controls insisting “Oh, no, after you,” like Chip ‘n Dale. The flip side of this is that a control which teleports itself to the “top” of the stack of controls can short-circuit the stack of other controls waiting to fire their methods, causing a debugging nightmare. Sometimes a Refresh will fire and sometimes it won’t. We can see some nice possibilities for this, but if you decide to resort to this tricky method, document it explicitly. Remember—some poor fool, perhaps even you, is going to have to debug this monster long after you’ve forgotten this trick.

Finally, Destroy events fire in the sequence of ZOrder in effect at the time the Destroy sequence starts. Mess with them within the Destroy event at the risk of your sanity.

During the Destroy events, attempting to push the ZOrder of a control back "over" controls that may have already been destroyed can crash FoxPro with an "Invalid Page Fault."

Example

* Here's a sample program that tests what ZOrder() will do
* The first creates an infinite loop, the second misses
* firing the Refresh() of one of the controls, and the third
* shows how the rules are different for Init()s in coded (vs.
* graphically designed) forms.
PRIVATE nDirection

* 1st example: Looping form
* Press any key other than Enter or Spacebar to stop the show.
WAIT WINDOW "An infinitely looping form"
nDirection = 1  && force the control to the back
oForm = CREATEOBJECT('BadForm')
oForm.Show()
oForm.Release()

* Second example: Button2's Refresh never fires
WAIT WINDOW "Form that skips Button2's Refresh"
nDirection = 0  && force the control to the front
oForm = CREATEOBJECT('BadForm')
oForm.Show()
oForm.Release()

* Last example: Inits in coded forms all fire
WAIT WINDOW "Form with Inits that should skip but don't"
nDirection = 0  && force the control to the front during Init
oForm = CREATEOBJECT('BadForm2')
oForm.Show()
oForm.Release()

RETURN

DEFINE CLASS BadForm AS Form
  ADD OBJECT btnCommandOne AS btnCommand WITH Top = 10
  ADD OBJECT btnCommandTwo AS btnCommand WITH Top = 50
ENDDEFINE

DEFINE CLASS btnCommand AS CommandButton
  PROCEDURE Refresh
   WAIT WINDOW "Hello, I'm " + this.name + ;
      "'s Refresh Event" + chr(13) + ;
      "Refresh Events fire in ZOrder()." + chr(13) + ;
      "Press Enter to continue," + ;
      " any other key to quit " ;
      TO cTheirAnswer
   IF EMPTY(cTheirAnswer)
      This.ZOrder(nDirection) && change order
   ENDIF
  ENDPROC
ENDDEFINE

DEFINE CLASS BadForm2 AS Form
  ADD OBJECT btnCommandOne AS btnCommand2 WITH Top = 10
  ADD OBJECT btnCommandTwo AS btnCommand2 WITH Top = 50
ENDDEFINE

DEFINE CLASS btnCommand2 AS CommandButton
  PROCEDURE Init
   WAIT WINDOW "Hello, I'm " + this.name + ;
      "'s Init Event" + chr(13) + ;
      "Hand-coded Init Events all fire " + ;
      "regardless of ZOrder()." + chr(13) + ;
      "Press Enter to continue"
   This.ZOrder(nDirection) && change order
  ENDPROC
ENDDEFINE

See Also

Box, Circle, Line Method, PageOrder, TabIndex