Developing a Database Application:
|
Creating The Menu |
The goals for Phase VII of this tutorial project are:
A Menu is the text interface that appears at the top of most Windows applications, and depending on the design, sometimes on forms that appear in the application.
The example application we are building uses a single menu attached to the application framework (_app.framewin), which is how we will also create our application. (This is because we are creating an MDI application -- if we were creating an SDI application, the menu might appear on each form.) While we do this there are decisions that affect the whole design of the application that must be considered.
For example, you could have an application that was SDI (Single Document Interface) -- which (normally) allows only one instance of any data form to be open at one time (although a developer could allow multiple instances of any one form). You could, instead, have an application that was MDI (Multiple Document Interface), which allows two or more instances of the same form to be open at one time and indeed allows multiple instances of the same form to be open at once. (It is possible to do a weird cross of MDI and SDI, which is how a lot of our applications work currently ... but let's not get into that here.)
The big differences between SDI and MDI (and this is a very quick over-view) are:Information provided by Gary White
- MDI forms are all contained by the application frame window (_app.framewin);
- The menubar window (the window that holds the menubar object, or the menu) for an SDI application appears on each form and for an MDI application appears on the application frame;
- The toolbar window (the window that holds toolbars) does the same as the menubar window;
- The "Window" menu (the menu used to select the 'current' form out of the ones that are open) is not applicable in an SDI application (or you must write a bunch of code) and in an MDI application it is automatic (see below);
- In dBASE SE, each SDI form gets its own entry on the task bar;
- In MDI applications, forms/windows are true child forms of the application form/window and minimizing/maximizing affects all of the child forms. Restoring one maximized form restores all maximized forms ...;
- If you minimize an MDI frame window, it minimizes the whole application, where in an SDI application, each window is treated separately;
- MDI child windows may be resized, minimized and maximized; SDI windows may or may not offer that functionality.
This application will be an MDI application, which means that we need to remember that the form that currently has "focus" may change rapidly -- this will mean that as we create various aspects of the application, we will need to deal with some flexibility in our code.
The menu we design will have to have options for opening forms, generating reports, a menu to handle selecting which form to set focus to, and so on.
When you want to create a menu there is a special design surface. As with the other design surfaces in dBASE SE, this is a "two-way-tool" -- in other words, the designer generates code and we can manipulate that code in the source editor and have it respected in the design surface. This is sometimes an easier way to fix any problems found in the code or simply to add or modify the existing code.
Menus are associated with forms (you must have a form to attach a menu to). In order to design a menu, let's go to the "Forms" tab of the navigator.
Double click on the third "Untitled" icon in this section of the navigator. This will bring up the menu designer.
This may not look like much, but it's pretty easy to use.
To put a menu option in, simply type the text you want to appear. If you want to have a hotkey that is activated with the <Alt> key and the letter for that menu (these are individual letters in menu text that are underlined, and calling their functionality is as simple as pressing the <Alt> key and the underlined letter, e.g., File) you need to preface that letter with an ampersand (&).
Let's create the "top" level of menu options for our application. These are the menu options that appear at the very top of the form, and if you click on them, will bring up a "sub-menu". The "top" level menu options are contained by the menubar object. The other menu options you will create will be contained by the menu option above them (or to the left of them).
Start by typing in the first position: &File and then press the <Tab> key on your keyboard. This moves you over to the right one position. Type: &Reports and <Tab>. Type: &Help. At this point, we're going to use a sneaky trick and have dBASE SE do the work: go to the "Menu" menu option, and select 'Insert "Window" Menu'. dBASE SE inserted this to the left of the current position, which is why we put the "Help" menu in before inserting the Window menu.
The option to insert the window menu adds a single line of code into the source of the menu for the constructor code that tells dBASE SE to handle the MDI windows automatically. (See note about a bug in the Window menu and Windows 95/98 below.)
Save your work so far, by pressing <Ctrl>+S, and when asked for a name, enter "Tutorial" and click "Save".
The File Menu
Next we need to go back to the File menu and add the options we need
there. To get back to the File menu, you can do it by either using the
mouse and clicking on it, or using a "back-tab", which is simply <Shift>+<Tab>.
A consideration for your menu that may not seem obvious is that you should name each menu object (which is what we are creating right now by typing in text in the position we want to appear in the menu -- each of these is a menu object). If we do not name them, then dBASE SE will name them something very non-intuitive, like "MENU44", "MENU45", etc. This makes it difficult, when examining the menu code (or if you get fancy, modifying the menu from code) to know which menu you are manipulating.
We are currently positioned on the "File" menu object, so go to the inspector, and name this "File" by finding the name property, and typing "File", and then pressing <Enter>.
Note: If you do not press the <Enter> key here and simply leave this item in the inspector, it will revert to the previous value. You can also use the up/down arrows to save your value, but if you just go straight back to the designer without saving what you entered, it will go away. This is a bit frustrating, but is "as designed". Get used to it -- when you change values in the inspector -- if the field shown is yellow, you must press the <Enter> or the up/down arrow keys for it to "stick" -- this is how it works in all design surfaces of dBASE SE!
Click back on the design surface.
To add items in the menu under the word "File", simply use the down arrow. This will create a new menu object that is considered to be a sub menu of the File menu.
Type: &Open to give the prompt for this menu.
Name this menu object "Open" in the inspector.
Tab to the right from the "Open" menu, and note that we are given a new menu object to the right. We are going to list each of the forms that can be "opened" here, by entering them at this point. We will come back (as noted elsewhere) and add code that will fire when the user selects the form to open.
Do the following to add the new menu options that will appear under the "Open" menu:
You have just created a series of menu options that will only appear if the user selects the "Open" menu, and in the case of the "Lookup" menu, they will only see these two menu options if they select "Lookup". These are often called "nested" menus.
We need to add the next option, which is the "Close" option. So set focus back to the design surface (by clicking on it), click on the word "Open" in the "File" menu, and use the down arrow.
In this position, type: &Close. In the inspector, change the name of this menu option to "Close".
Use the down arrow again to add another menu option to the file menu. This time, however, we are going to put what is called a "separator" in -- this is a menu object that cannot get focus, and displays simply a line across the menu. It is used to separate different groups of menu objects from each other.
All that is necessary to create a separator is to go to the inspector, and select the separator property, and either double-click it, or use the combobox, and select "true". However, just to keep things straight, let's assign a name to this menu object in the inspector as well of "Separator1".
The last item for the file menu is the Exit option, which will be used to close the application. Use the down arrow and type: E&xit. Name this menu object "Exit" in the inspector.
You should see something like what is in the image below for your menu:
The Reports Menu
The reports menu needs the following sub-menu options.
Menu Text | Name | shortCut | separator |
---|---|---|---|
Supplier Information | Supplier | none | false |
Inventory Summary | Inventory | none | false |
Customer Statements | Statements | none | false |
none | Separator2 | none | true |
Customer Address Labels | Labels | none | false |
Note: It is possible to use the same menu names because of the way the containers work here. The "Supplier" menu under the reports menu is not going to conflict with the "Supplier" menu under the files/open menu, because dBASE SE sees them as being completely different entities. Example -- the file menu's path down to the supplier menu is:this.FILE.OPEN.SUPPLIERWhere the path to the one for the reports is:this.REPORTS.SUPPLIERTo dBASE SE these are very different, and so we can name them the same.
The Window Menu
This will have only one menu option that is always available, and we
will have other options added dynamically during the time the application
executes. This last part will be done automatically because of how we added
the "Window" menu option. In the meantime, the one option that is currently
in the menu is:
Menu Text | Name | shortCut | separator |
---|---|---|---|
Close &All | CloseAll | none | false |
We're going to add some simple code to the onClick event of the "Close All" menu. Click on that menu, click on the inspector, and select the "Events" tab. Select onClick and enter the following codeblock:
{;CLOSE FORMS}
Make sure you press <Enter> to save this value.
Note that if you accidentally pressed the tool button, you will find that the source editor appears with a "Function method" and "return" statement -- your cursor will appear between these on a blank line. You can remove this by going to the "Method" menu and selecting "Delete Method" -- this will remove this and you can go back to the Inspector.
Note: without getting into a lot of the details involved in writing codeblocks, the semi-colon at the beginning of a codeblock is required if your codeblock is a statement codeblock that has no parameters. You can also begin a codeblock with a pair of pipe characters (||). For more details on codeblocks see online help (keyword is "codeblock").
This code simply closes any open forms in the application. It's rather handy in that it's a shortcut method, useful if you have a bunch of open forms.
Bug: On a Windows NT computer, the "Windows" menu works exactly as expected. When an MDI form is opened, it automatically appears in the menu and if it is the active form, a checkmark appears by it in the list (assuming more than one MDI form is open, you will see a list the open forms.)
However, on a Windows 95 or 98 computer, for whatever reason, the Windows menu does not always get or display the proper window information. Do not despair, there is a fix below which is a simple change in a property of one of the custom forms ... by using this workaround, we can make the application work the same in NT or in Win 95/98.
The Help Menu
We're not really going to create help for our application
(that's
a topic that would take a LONG time to delve into), but we will add
an option to bring up the "About" dialog. This dialog is simply going to
provide some basic information about the application.
Menu Text | Name | shortCut | separator |
---|---|---|---|
&About Tutorial | About | none | false |
Save It!
Press <Ctrl>+W to save your work and exit the designer.
The Forms
Earlier in this tutorial we created a number of forms. Now the question is how do we make them work with our application?
The way we do this is to attach them to the "Open" menu options of the application's menu.
To do this bring up the menu in the designer. (Right click on "Tutorial.mnu" in the navigator, and select "Design Menu" or click on the menu and press <Shift>+<F2>).
We're going to add some code to our menu so we are not repeating a set of programming statements for each form we need to open (we are using object oriented code techniques this way). To do this, click on the "Method" menu at the top of the screen, and select "New Method".
You will see that dBASE SE has added the following to the source code of the menu:
function Method return
Change the code to:
function OpenForm( oForm ) // set the top/left properties so the form isn't // *right* on top of the current one ... if type( "_app.framewin.currentForm" ) == "O" oForm.top := _app.framewin.currentForm.top + 2 oForm.left := _app.framewin.currentForm.left + 2 else oForm.top := 0 oForm.left := 10 endif oForm.open() oForm.setFocus() return
The code we added above is going to:
Click on the "Open" menu option, then on the "Customers" menu option. In the inspector go to the onClick event and click the tool button. This will put you in the source editor as we've seen for other parts of the product.
Enter the following code:
function CUSTOMERS_onClick // open the customer form: local f f = new CustomerForm() // call the code we created elsewhere // to actually open the form class::openForm( f ) return
We need to do the same for the appropriate forms for each menu option. Change the statement "f = new CustomerForm()" to the correct form for each of them ... remember that copy and paste is a useful feature of Windows (rather than retyping the code).
While we are here we should add the code that will be used to close the currently active form.
function CLOSE_onClick // close current form if there is one if type( "_app.framewin.currentForm" ) == "O" _app.framewin.currentForm.close() else msgbox( "The currently active form cannot be closed "+; "with this option.","Can't do it!", 64 ) endif return
Add the About Form
We need to add code to the menu to actually open this form. Since it
is a modal dialog, we will have to open the procedure file, but otherwise
the code is pretty basic. Bring the menu back up one more time in the designer.
Click on "Help" and then "About Tutorial". In the inspector, click on the onClick event, and the tool button. This will bring up the source editor, and enter the following:
function ABOUT_onClick set procedure to about.wfm additive f = new aboutForm() f.readModal() close procedure about.wfm return
Save the menu and exit.
We have three reports and one set of labels to hook to the menu. The Invoice report is called from the invoice form, so there is no menu item for this report.
Although a report can be rendered straight to a printer, we are going to render them in a preview form for on screen viewing. When you ran the Tutorial Setup archive, a file named Preview.wfm was copied to the tutorial's main folder. We will use this form to view our reports. If the application's user wishes to print the report the preview form has buttons for that purpose.
To attach the reports in the menu, select the report menu, and then the first menu item listed, which should be "Supplier Information". Select the onClick event for this, and the tool button, and enter the following code:
set procedure to suppliers.rep additive fPreview = new PreviewForm() // to open with ReadModal() fPreview.bModal = true fPreview.viewer.fileName := "suppliers.rep" fPreview.Open()
Do the same for the inventory report:
set procedure to inventory.rep additive fPreview = new PreviewForm() fPreview.bModal = true fPreview.viewer.fileName := "inventory.rep" fPreview.Open()
and finally for the labels:
set procedure to customer.lab additive fPreview = new PreviewForm() fPreview.bModal = true fPreview.viewer.fileName := "customer.lab" fPreview.Open()
The "suppliers" report would look like the following using the preview form:
The statements report is a bit different than the other three reports on the menu. This report is not called directly from the menu, but rather, it is called by the getdates.wfm form. So the menu item will open the getdates.wfm dialog. This is a modal form so it's going to opened the same way we opened the About.wfm form.
function STATEMENTS_onClick set procedure to getdates.wfm additive local f f = new getdatesForm() f.readModal() close procedure getdates.wfm return
You have just created the menu that will be used with the Tutorial application project. However this menu is not fully functional until we build a start-up program for the application and attach the menu to the window of the application. Remember, our application is MDI, which, among other things, means that the menu does not appear at the top of each form. Rather it will appear at the top to main application window. In the next section we will create a start program which among other things will attach our menu to the application window.
Let's move on to the next part of the tutorial.
Last Modified: October 25, 2002
The Legal Stuff: This document is part of the dBASE SE Tutorial created by Ken Mayer and Michael Nuwer. This material is copyright © 2002, by Ken Mayer and Michael Nuwer. dBASE SE 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 authors, who retains all rights to the document.