Developing a Database Application:
Creating the Startup Program
The goals and objectives of this phase of the tutorial project are:
An interesting concept that we will adopt in our sample application is the idea of creating an object specifically for controlling the application.
The advantage to this is that we can create our own methods (such as "startup" and "shutdown") to handle specific code for the application.
It also means we are encapsulating the whole application within this object (which is proper for OOP). We will be taking this a step further by creating a generic application object and then subclassing it for this particular application.
Note: "Subclassing" is the technique of using a class as a "base" class -- creating a new class that inherits all of the properties, methods, and events of the base class.
An example of this is creating a form in dB2K (never mind "custom" forms) -- there is a "base" form class that is built in to dB2K. When you create a new form, it is a subclass of the base form. It has all the properties, events and methods of the standard form in dB2K.
Once you have subclassed an object you can modify its properties, events and even hook your own code into the methods of that class, without modifying the base class's definition.
To do this will mean writing a START program to handle creating the application object. The program will be used to actually set up whatever procedure files (and custom control files, etc.) are necessary for the application, create an instance of the application object, and execute the startup code.
To create the program go to the Navigator, click on the "Programs" tab, and double-click the "Untitled" icon. This will bring up the source editor with nothing in it.
You should probably place some comments at the beginning, explaining what the program is. When placing comments in the code, you should either use the comment "block" comments (as shown below) which start with "/*" and end with "*/", or place two slashes at the beginning of each line ("//"). If you are used to using the older asterisk (*) and double ampersand (&&) style comments, these work fine as well. Comments can be useful for you or anyone else examining your source code:
/* START.PRG Author: Ken Mayer Date..: November 8, 1999 Tutorial project START program This program is used to start the TUTORIAL application. There is a reference to another .CC, which is the genericMDIApp.CC file -- this contains the generic application object, and below we subclass it ... The idea is that you have a lot of "things" that you always do for any application that is an MDI app. You create one object that handles all of that, and then you subclass that for the specifics for an application, as each is at least slightly different. */
I am not going to spend a lot of time discussing such things as variable scoping (using local versus public, and so on). If you need to know more about these, they are discussed in the online help that ships with dB2K, as well as the Language Reference and the Developer's Guide.
The following code is the very beginning of the application. It can be used "as is":
local app set talk off set procedure to GenericMDIApp.cc additive app = new TutorialApp() return ( app.open() )
What the statement "app = new TutorialApp()" does is to create an instance of the tutorial application object. We will define this a bit at a time in the next steps. The "return" statement simply calls the app.open() method, which is code we will create -- this will be a method of the application object.
The Application Specific Object
In the same program file, you want to add the following (after the RETURN statement), which is the actual application specific part of the code:
class TutorialApp of GenericMDIApp // set any specific code for the subclassed // MDI application here: this.FrameWinText = "dB2K Tutorial Project" // note this is not the file name -- the // SETUP program must execute "set procedure ..." that // will open the file "dB2KTutorial.mnu" ... the class // will be available from that point on. This is // the classname of the menu: this.MenuClassName = "dB2KTutorialMenu" endclass
Note that in the statement above: 'this.MenuClassName = "dB2KTutorialMenu"' -- there is NO SPACE between "Tutorial" and "Menu".
This code (or class) gets loaded into memory when the "app" object is created with the command "new TutorialApp()". There isn't much code in this class because this is class (TutorialApp) is subclassed from another class called "GenericMDIApp" (Which we still need to write). This subclassed object merely sets two custom properties. The rest of the work will be done in the super class (in GernericMDIApp).
We now need to write the code for GernericMDIApp so save the START program (<Ctrl>+S, name it "START").
The next file that we need to create is the generic application class.
In the command window type (or, in the navigator, double-click the "Untitled" icon under the Programs tab -- make sure when you save the file that you name it properly):
create command GenericMDIApp.cc
This will bring up a second window in the source editor (you can switch back and forth with the tabs ...).
We need to create a class. Interestingly enough, to create an object in dB2K that is not based on a stock object (one of the ones built-in to dB2K), the syntax is as simple as:
This tells dB2K we are creating our own class, called "GenericMDIApp".
We can create properties and methods, but not events. For our purposes, we need at least two methods, one to handle opening the application, the other to deal with shutting it down (this should handle any cleanup necessary).
We also need to have some code that sets some custom properties, and does whatever other setup for the application object that is necessary. This code is executed for each instance of the object, when the object is instantiated (app = new ...). because it is placed before the methods we will be defining. This is called the "constructor code".
// This custom property should be overwritten // in a subclass, or after the class is created, but // before the Open() method is invoked: this.FrameWinText = "Generic MDI application" // The same goes for this custom property: this.MenuClassName = "MyMainMenu" // We assume here that every MDI app will have // a SETUP.PRG do setup // Assign a property to _app.frameWin, which is // a reference to this object: "this". _app.framewin.app = this
The "Open" Method
For our purposes, the open method will not be real complicated. It will perform a few basic tasks -- turning off the application shell (see "shell()" in online help), turn off the standard dBASE toolbar and statusbar, and set the menu we created in the previous part of the tutorial as the current menu.
Function Open // set a reference to the menu private c // build the command (a 'macro'): c = 'this.rootMenu = new '+this.MenuClassName+; '(_app.framewin,"Root")' // execute it: &c. // Make sure no forms are open close forms // Make sure we're not in "design mode" set design off // set the <Escape> key "off" but store the // current setting: this.OldEscape = set("ESCAPE") set escape off // Turn off the Visual dBASE shell, but // leave the MDI frame window: shell( false, true ) // Turn off the application's speedBar and statusBar _app.speedbar := false _app.statusBar := false // Set the text property for the application's framewin _app.framewin.oldText = _app.framewin.text _app.framewin.text := this.FrameWinText
Note:The first three commands in the Open method shown above are used to create a "macro" command and execute it -- this is a way of building a program statement that needs to be executed but also needs to be very flexible. By building a character string, we can insert variable values (in this case, the name of the menu class), and then once the whole string is built, we can execute the command in one shot (&c.).
For more information on using macro expansion, see "&" in online help ...
Before proceeding we must address a bug that was mentioned in the Menu part of the tutorial. When our application opens, the above code attaches our menu to the application's main window, the WinFrame. However, for some reason when a form is opened in the WinFrame the menu disappears. To get around this problem we need to set the menuFile property for all the forms for which the the menu used. For whatever reason this works to get around the problem.
But wait, OOP makes this modification an easy task. We really only need to add this property to one custom form since all the child forms will inherit the property. Therefore, use the navigator and open DataForm.cfm in the designer. Bring up the Inspector and enter the text "db2ktutorial" in the menuFile property (and press <Enter> or this will not "take") OR click on the tool button and select "dB2KTutorial.mnu" from the dialog that appears. This should not be necessary, but ... We are not using the menuFile property on the BASE and DIALOG forms because some of the forms derived from these are modal dialogs and the menuFile property would place the menu on these forms.
The "Close" Method
This method will handle closing forms and cleaning up after the application, including resetting, if returning to the development environment, various changes that were made in the OPEN method above.
function close // close any forms that might have been left open close forms // if we are in the "runtime" environment (the executable), // we want to "quit" (otherwise the framewin will // be left on screen and dB2KRun.exe will be left // in memory!) if ( "runtime" $ lower( version(0) ) ) quit else // otherwise we're in the IDE, let's reset some // values: with ( _app ) framewin.app := null framewin.text := framewin.OldText speedbar := true statusBar := true endwith // go back to design mode: set design on // set escape back to whatever it's previous // state was: cEscape = this.oldEscape set escape &cEscape. // close any open procedures set procedure to // release the menu _app.framewin.root.release() // set the shell back ... shell( true, true ) endif ENDCLASS // don't forget this!
Note: The "with/endwith" construction is new to Visual dBASE 7.x, and is very useful when working with objects. You will see this code a lot if you examine code created by the form or report designers. The main purpose is to shorten your code -- see online help for details.
There is one caveat with the with/endwith construct -- you cannot create new (custom) properties of an object inside this code. You must create it outside the with/endwith construct.
Save this file and exit (<Ctrl>+W) ... this will leave the original START.PRG in the source editor. We need to add the application specific object here.
The next thing we need to do is create a SETUP program. The reason for this program is that it is used in two ways -- 1) It is called from the GenericMDIApp to make sure that all necessary procedure files and other code is open (using the ADDITIVE clause) and available at all times; 2) When developing the application, you need a way to call all this same code.
Rather than putting the code in two different places, we put it in SETUP.PRG, and it is then available either by running the START program, or by simply running SETUP ...
Create a new program in the source editor (double-click the "Untitled" icon in the navigator under the "Programs" tab), and enter the following:
/* SETUP.PRG The "setup program" for the MDI Tutorial application ... open any and all files needed to run the application and/or develop the application. */ // These always get me -- if the program // crashes, and they usually do while developing -- // (due to programmer error), the speedBar and the // statusBar in the IDE is not available ... this // just puts them back. the MDI application class turns // them back off ... _app.speedbar := true _app.statusBar := true // this can also cause problems: set design on // Set procedures ...: set procedure to START.PRG additive // make sure the menu is available: set procedure to db2ktutorial.mnu additive // custom controls used by the application set procedure to ":classes:seeker.cc" additive set procedure to ":classes:report.cc" additive set procedure to ":tutorial:MyControls.cc" additive set procedure to ":tutorial:CustomReportControls.cc" additive // add others as needed here:
Most of the forms also need to have their code available, just like a procedure or custom control file, so that we can just call them as needed. This is also done in the SETUP program. So before doing anything else, Add the following statements to the above file:
set procedure to COUNTRY.WFM additive set procedure to CUSTOMER.WFM additive set procedure to INVENTORY.WFM additive set procedure to INVOICE.WFM additive set procedure to STATE.WFM additive set procedure to SUPPLIER.WFM additive set procedure to PREVIEW.WFM additive
We could load and unload these procedure file in the menu, when the form is called. You may have noticed that is what we did with the report files. But that would mean that each time a form is called, the procedure file would need to load before the form is opened which will degrade the applications performance. When you proceed to developing your own application, I would suggest in general that you would want to load the procedure files for the forms that are used the most, and open/close the rest as needed. The advantage to opening all of the forms in the beginning when you call a form, it appears pretty close to instantaneously on screen -- speed is an issue for some folks. (Note that we do not need to do this for the InvoiceEdit and LineItemEdit forms as they are "dialog" forms that are only opened from the Invoice form.)
The setup program can also include any SET type commands, such as "SET EPOCH" or others (for the application you should consider storing that kind of "command" in the application's .INI file ... we'll look at that when we get to deploying the application).
When done entering your commands as shown above, save and exit the source editor (<Ctrl>+W, and enter "SETUP" as the name of the program).
Test the Start Program
We really ought to test this to make sure that it works ...
All you need to do is either double-click the "START.PRG" program icon in the Navigator, or type, in the command window:
Note: If you made mistakes (typos) typing code in, errors will occur as you execute the start program. Don't panic!
You may want to select the "Fix" button, and compare the code against what is shown here. The code here does work -- it was pasted in from a working application. Make corrections and save (<Ctrl>+W).
If you fixed the problem, the program will continue where it left off.
If you did not (or another error exists in the code), another error message will occur ...
At the very worst, you may get a GPF. Again, do not panic. There are some bugs in the dB2K error handling mechanism that may be causing this problem. Select the "No" button when asked about saving your work, and then restart dB2K. Run the "SETUP" program to restore the environment ...
Bring the START program, or the SETUP program, or the GenericMDIApp custom class file into the source editor and compare (again) against the listings here ...
You should see everything change, the menu should be the one we designed earlier, the titlebar should show the text we defined, and so on. One problem exists ...
Try selecting the "File" menu, and then "Exit" ... nothing happens. You're STUCK! Well, not really ... use the 'x' button in the upper right of the title bar (this will close dB2K) ... You will need to restart dB2K, and then run the SETUP program (this will reset the toolbar, and such).
Back To The Menu
We want to go back to the menu we created earlier, and add some code. The only code left to add is code that is executed when the user "Exits" the application. Otherwise the same thing will happen to you as above each time you test the application ...
We need to bring the tutorial menu back into the designer. To do that, click on the "Forms" tab, and right click on "dB2KTutorial.mnu" in the navigator. Select "Design Menu" or press <F2> to bring this up in the designer.
Click on the "Exit" menu object which appears under the "File" menu. In the inspector, click on the "Events" tab, and then on the onClick event. There is a "tool" button -- click that. This will bring up a source code editor window, and you can write code for the menu object's onClick event:
function EXIT_onClick return ( _app.framewin.app.close() )
You may want to press enter after the second line (one tester of this tutorial found it didn't work without that ...).
Press <Ctrl>+W to save and exit.
Re-testing the START Program
Let's try this again ... in the Navigator, click on the "Programs" tab, and double-click the program called "START".
Select the "File" menu, and notice the options. Try the other menus ...
Finally, select the "File" menu, and then the "Exit" option. This should bring you back to where you were ... much better than before.
The Legal Stuff: This document is part of the dB2K Tutorial created by Ken Mayer. This material is copyright © 2001, by Ken Mayer. dB2K is copyrighted, trademarked, etc., by dBASE, Inc., the BDE (Borland Database Engine) and BDE Administrator are copyrighted, trademarked and all that by Borland, International. This document may not be posted elsewhere without the explicit permission of the author, who retains all rights to the document.