But at this point, we need to examine how the main and child layout scripts operate, and interact. Let's first study the following script:
/* Main message loop. */ DO FOREVER GuiGetMsg() SAY GuiSignal END GuiGetMsg: /* Continue handling user input and calling appropriate functions * until someone sets "GuiSignal" to something. Then finally return * to the main message loop. */ GuiSignal = "" DO UNTIL GuiSignal \== "" SAY "Type 1 or 2 then press ENTER >" PULL line IF line == 1 THEN WM_CLICK_One() IF line == 2 THEN WM_CLICK_Two() END RETURN WM_CLICK_One: RETURN WM_CLICK_Two: /* Set "GuiSignal" to something so that GuiGetMsg will return to * the main message loop. */ GuiWake("Wake up") RETURN GuiWake: GuiSignal = ARG(1) RETURNThe DO FOREVER loop calls GuiGetMsg. Notice that GuiGetMsg handles the interaction with the user (via a PULL instruction). GuiGetMsg does not immediately return to the main level. Instead, it loops around, examining user input and calling various handlers (ie, WM_CLICK_One or WM_CLICK_Two) depending upon what the user types. GuiGetMsg does not return until some handler calls GuiWake to set the GuiSignal variable to something other than an empty string. After GuiWake is called to do this, GuiGetMsg finally returns to the main level. That's when the main level finally resumes executing. It simply displays the value of GuiSignal, and then loops back to let GuiGetMsg handle more user input, and call more handlers, until some handler once again calls GuiWake.
Study the above script carefully and make sure you understand it. Perhaps step through each line in Programmer Center's debugger, and type a 1 or 2 for user input, to make sure you fully comprehend the way it works. The above skeleton script is the basis for how Reginald's REXX GUI add-on works, with two differences:
The Main Window Layout script
Now let's examine the actual Main Window Layout script that Programmer Center created for you. Flip to the editor showing ListOfNames.rex.
The first instructions simply load the GUI add-on, since it comes in DLL form. We use a LIBRARY statement to load the REXXGUI.DLL and register its functions.
Next we set a couple specially named variables, GuiErr and GuiHeading, to tell Reginald how we want errors in the Gui functions reported for our main script. We ask Reginald to raise a SYNTAX condition whenever an error happens. (We'll discuss the error handling features of Reginald's GUI in more detail later).
After this, we call GuiCreateWindow to create our main window based upon what is inside of that mysterious comment that appears at the top of our window layout script (ie, the one you weren't supposed to edit). This creates and shows the window. But the user can't interact with it yet until we call GuiGetMsg.
Finally, you'll notice a DO FOREVER loop around a call to the GUI function GuiGetMsg. We'll refer to this as a "message loop".
When the main script calls GuiGetMsg without passing any arguments, then GuiGetMsg does not return immediately. Instead, GuiGetMsg loops around, managing the user's interaction and all of the processing associated with that. In other words, while the user is interacting with the windows/controls, the main script is locked inside of that call to GuiGetMsg, just like the above example script.
While in GuiGetMsg, the user is free to manipulate any open windows (and controls within them) in any order that he wishes. (The exception to this is for a modal window, which blocks the user from interacting with all other open windows except that one modal window and its controls). Reginald's GUI may call various event handlers that you have added to your window layout scripts. These may be called at any time, and in any order, depending upon which open window and which control the user operates. Reginald may even call some event handler in your main script (if an event happens with your main window or its controls).
So when does GuiGetMsg finally return? It returns only when there has either been an error condition raised (which was not handled by the window layout script in which the error happened), or one of your handlers calls the GuiWake function.
When GuiGetMsg returns, then the user can't interact with the windows any more (until the main script calls GuiGetMsg again). So, think of GuiGetMsg as a way for the main script to pass control to Reginald's GUI (and the user). When GuiGetMsg returns, it is passing control back to the main script (and away from the user). The variable GuiSignal will be set to whatever string was passed to GuiWake, or be DROP'ed if what caused GuiGetMsg to return was not a call to GuiWake (ie, could have been an unhandled error). The variable GuiObject will be set to the object variable of the child window script that called GuiWake, or it will be dropped if it wasn't a child script that called GuiWake. The skeleton script doesn't do anything meaningful when GuiGetMsg returns, but there are some comments and conditional tests of GuiObject and GuiSignal, ready for you to add your own instructions to do something meaningful.
Incidentally, for error handling, we put a CATCH SYNTAX inside of the DO FOREVER loop where GuiGetMsg is called. We simply display an error message, and then loop back to continue our message loop (so that an error doesn't abort the script).
The remainder of the main script are any event handlers we added for our main window and its controls.
The Child Window Layout script
Now flip to the editor window containing the child layout script (AddAName.rex):
So, it may have a Create function that is called when the main script does that CreateObject call. (ie, CreateObject calls the child script's Create function). And it may have a Destroy function that is called when the main script does a DROP on the object variable name. (ie, The DROP instruction calls the child script's Destroy function).
Let's look at the Create function. Just like in our main script, we first set a couple specially named variables, GuiErr and GuiHeading, to tell Reginald how we want errors in the Gui functions reported for this child script. We ask Reginald to raise a SYNTAX condition whenever an error happens. Note that these two variables are not the same as the ones in the main script. After all, each script has its own variables. What this means is that each script can choose how it wants errors reported, and two different scripts can use entirely different methods.
Next we call GuiCreateWindow. This creates the child window based upon what is inside of that mysterious comment that appears at the top of our child window layout script. This creates the child script's window. But the user can't interact with it yet until we return from Create (and CreateObject) and the main script gets around to calling GuiGetMsg.
There is one difference in the GuiCreateWindow call here, as opposed to the main script's GuiCreateWindow. In the child script, we pass the 2 trailing arguments that the main script passed to CreateObject. If the first arg is -1, this is what causes the child window to be created modal, and will prevent the user from interacting with the main window while the child's window is open. The second arg is either 'NORMAL' to display the window, or omitted to defer displaying the window. (Later, we'll see the use of the latter when we discuss persistant data).
Finally, Create returns to the main script.
Now let's look at the Destroy function. All it does is call GuiDestroyWindow to destroy the child window that was created in the Create function.
GuiWake()
Let's add a handler for the OK control in our "Add a name" window. Make sure that you have our AddAName.rex child script displayed in an editor window.
Click on the (default) window to open up the "Add a name" window.
Double-click on the OK control to pop open its "PUSH Properties".
Highlight the CLICK event and click on the Add event handler button. This adds a new subroutine named WM_CLICK_NameOk to our child window layout script. This subroutine is automatically called whenever the user clicks on the OK button. Add the following instructions:
WM_CLICK_NameOk: /* Get what the user typed into the ENTRY. It has a * variable named 'NewName' associated with it. */ GuiGetCtlValue('NewName') /* Did the user type something into it? If so, call GuiWake * to cause the main script to return from its GuiGetMsg. * We'll pass a signal that is simply what the user typed. */ IF NewName \== "" THEN GuiWake(NewName) RETURNWhat we've done above is call GuiGetCtlValue, passing the name of the REXX variable that we associated with the ENTRY control. This sets the variable to whatever the user typed into the ENTRY. Next, we check that he did indeed type something (ie, it isn't an empty string). If he did type something, then we call the GuiWake function. This function causes the main script to return from GuiGetMsg. Its GuiSignal variable will be set to whatever we pass to GuiWake. Above, we simply pass whatever the user typed. The main script's GuiObject variable will be set to the name of the object variable of whomever called GuiWake. Here it is our ADDANAME object that called GuiWake.
Note: You can pass whatever you want to GuiWake, and use it for whatever purpose in your main script. Reginald's GUI is very open-ended in this regard. Above, I've simply made the decision to use GuiWake's signal arg as a way to return the contents of the ENTRY to the main script.
Now it's time to flip back to our main script ListOfNames.rex editor window.
Look for the following comment:
/* Here you add a WHEN clause for each child object variable name. You * must uppercase the name. Then look at GuiSignal and do... whatever. */Let's add some instructions to the main script to do something with the signal that our child script sent. Add the following instructions after the above comment, replacing the line "WHEN 0 THEN NOP".
/* Check for the ADDANAME object script signaling us. * Note: You MUST uppercase the object variable name. */ WHEN 'ADDANAME' THEN DO /* Let's display the signal sent to us. */ GuiSay(GuiSignal) /* Now that we're done with the child window, DROP its. * object variable. This also closes the child's window. */ DROP (GuiObject) ENDNow run the ListOfNames.rex script. Click on the Add button and you'll see the "Add a name" window open. Type in a new name and click the OK button. You should see a message box pop up with whatever you typed. This is the main script doing that GuiSay of the value of GuiSignal. After you dismiss the message box, you'll see the "Add a name" window close. This is the main script DROP'ing the child.
Well, the GuiSay proves that we're getting communication between the child and main script, but let's substitute that GuiSay call with something more useful. Let's add the new name to the listbox. Make the following changes to the instructions:
/* Check for the ADDANAME object script signaling us. * Note: You MUST uppercase the object variable name. */ WHEN 'ADDANAME' THEN DO /* Add the new name to the list. Note that we associated the * variable NameAdd with that list box. */ GuiSendMsg("NameList", "ADDSTRING", , GuiSignal) /* Now that we're done with the child window, DROP its. * object variable. This also closes the child's window. */ DROP (GuiObject) ENDNow, run ListOfNames.rex and try adding a new name. You'll see what you entered appear in the list.