═══ 1. What is Lancelot? ═══ Lancelot is a personnel data tracking tool. It tracks general personnel information, badge, status, skills, system account information, projects, tasks, and your timecard. This sample application is intended to show integration rather than optimum performance or coding. Lancelot was written by our QA group to utilize some of the more complex objects provided by the IBM Open Class: User Interface Class Library. Lancelot primarily utilizes these objects:  IFrameWindow  IMenuBar  Infoarea  IHelp  IFont  IResourceLibrary  IContainerControl  IContainerObject  IPopupMenu  INotebook  IMultiCellCanvas  ISetCanvas  IProfile  IEntryField  IPushButton  IGraphicPushButton  INumericSpinButton  IComboBox  ICheckBox  IMenuBar  ITitle  IString  ICommandHandler  ICnrHandler  ICnrMenuHandler  IDM*  IToolBar  IToolBarButton  IFlyOverHelp  IFlyOverHelpHandler  IGPie  IGRectangle  IGString In addition, Lancelot uses a simple flat database implemented with IProfile. However, this flat database is used only to show data interaction with the GUI objects and avoid requiring a relational database installed on your system. Note: You should NOT use the flat database provided for any of your own applications! We suggest you don't even look at the internals of the database code. The files you should avoid are ld*.*pp. You should use a true relational database for your applications. ═══ 2. Lancelot Usage ═══ To create Lancelot, you must first compile and link the application and it's components.  On AIX, issue make  On OS/2, issue nmake  For other environments, issue make To execute Lancelot, simply issue lancelot ═══ 2.1. Lancelot Main window ═══ Lancelot's main view is what you see when the application first comes up. It contains the Query Personnel object and any private query objects you may have created. The Query Personnel object queries employee information dependent on your search criteria. You have the option of saving the search criteria in the main view for faster access next time. ═══ 2.2. Lancelot Query Information window ═══ Lancelot's Query Information window contains a notebook with pages for specifying personnel search criteria. The pages available are: General Page General information for a person. Accounts Page User accounts and systems for a person. Skills Page Individual skills for a person. Badge Page Badge information for a person. Status Page Employement status information for a person. The buttons available are: Query Query the personnel database given your search criteria. If persons are found matching your criteria, a window appears with objects representing each person. Save Save your search criteria to the main view window for each access next time. Help Provide help for a specific page ═══ 2.3. Lancelot Personnel window ═══ Lancelot's Personnel view is what you see when the application finds personnel matching your search criteria. It contains an object for each person. By opening an object, the Employee Information window appears with specific information for this person. ═══ 2.4. Lancelot Employee Information window ═══ Lancelot's Employee Information window is displayed to show employee information for a single employee. Any changes to the employee information will be saved in the database. However, the Undo button can undo any changes you entered. The pages available are: General Page General information for the person. Accounts Page User accounts and systems for the person. Skills Page Individual skills for the person. Badge Page Badge information for the person. Status Page Employement status information for the person. Project Page Projects the person has worked on. Tasks Page Work tasks the person has worked on. Timecard Page Timecard for the person. The buttons available are: Undo Undo any changes to the employee's information you entered. Help Provide help for a specific page ═══ 3. Lessons Learned ═══ In coding Lancelot, we learned alot by the fine nuances of coding GUI applications. ═══ 3.1. General ═══ There are several general things we learned in coding Lancelot.  When instantiating IWindow objects with the new operator, you must make sure to delete these objects when finished. However, there are times when you don't know how many windows you will have. Therefore, you can use setAutoDeleteObject(). This function deletes the IWindow object for you. Example: IFrameWindow* myFrame = new IFrameWindow( ID_FRAME ); myFrame->setAutoDeleteObject( true );  IProfile changes are not visible until the IProfile object is destroyed.  There are two methodologies to using event handlers. 1. Create your own event handler class that inherits from an event handler 2. Multiply inherit from some visual object and from an event handler The first method is preferred. See LMainWindow (lmainwin.cpp) for an example. However, the second method requires less coding. See LInfoWindow (linfowin.cpp) for an example.  If you do not specify a title string or define an ITitle for a frame window, the frame window will get its title from the resource file if the resource Id for the frame window is specified in the string table. ═══ 3.2. Containers ═══ There are several things we learned about containers in coding Lancelot. Here are some of the items we learned.  In order for a container to show detailsView, the container must know about offset (binary location) information for individual fields in a data class (data structure). There are two approaches for the container to query a data class' fields: 1. Make the data class a friend of the container. Then the container can use something like this when creating it's columns: IContainerColumn lastNameColumn( offsetof( EmployeeCnrObject, theLastName ) ); 2. -or- A better solution is to create a function in the data class for each data field. The function would do the following: unsigned long EmployeeObject::lastNameOffset() { return ( offsetof( EmployeeCnrObject, theLastName ) ); } The container column would then look like this: IContainerColumn nameColumn( EmployeeCnrObject::lastNameOffset() ); The only advantage to using option 2 is that any container can get the data class field offsets without having to be friends. The disadvantage is that if you have alot of data fields, you will have to code query functions for each. A good example of each can be found in the class LMainCnr (lmainwin.cpp) and LPersonnelCnr (lperswin.cpp).  If you have a container object that contains your own data class object and you want your data class object's private data fields to appear in the container, do not specify the offset for your data class object itself. Instead, specify the offset of the container object's variable name representing your your data class and reference the data class object's internal data field. Example: IContainerColumn lastNameColumn( offsetof( EmployeeCnrObject, privateData.theLastName ) );  In TreeIconView, the container allows only single selection. Therefore, we must disallow the Select All function since only the last cnr object will be selected. And we must disallow the Deselect All function since an exception will be thrown. ═══ 3.3. Notebooks ═══ There are several things we learned about notebooks in coding Lancelot. Here are some of the items we learned.  When a page is added to a notebook and using the autoPageSize style, the pages are automatically resized to the current notebook size. Therefore, we need to resize the notebook pages to their minimum size so that the notebook can properly calculate it's minimum size. See LInfoNotebook (linfonb.cpp) for an example.  In order to property size notebook tabs based on the current font size, we sized the major tab width to be the ( average character width for the current font ) X ( number of characters we want to display ). ISized the major tab height to be the ( average uppercase height for the current font ) X 2. setMajorTabSize( ISize( notebookFont.avgCharWidth() * ID_INFO_NOTEBOOK_TAB_CHARS, notebookFont.avgUppercase() * 2 ) ); See LInfoNotebook (linfonb.cpp) for an example. ═══ 3.4. Canvases ═══ There are several things we learned about canvases in coding Lancelot. Here are some of the items we learned.  For aligning like objects such as radio buttons, ISetCanvas does the best job aligning.  For aligning unlike objects such as entryfields and static text, IMultiCellCanvas does the best job aligning.  When aligning unlike objects, a minimum size for one object is usually too large for an unlike object in the same column. Therefore, place the unlike object in a different column (usually one column more is sufficient). ═══ 3.5. Help ═══ There are several things we learned about help in coding Lancelot. Here are some of the items we learned.  General help is simply the panel associated with the HELPITEM in the help table. Example: HELPITEM ID_MAIN, ......  Help index and using help are usually generic. So we can use the generic system help by getting the id from IHelpWindow::index and IHelpWindow::using.  Keys help is always specific to the application. Therefore, we must write our own in the .IPF file. There can be two approaches to making keys help work: 1. In the command() function, case off the id defined in the .RC for the user selecting "Keys help" and then calling help.show( id ) that is defined in the .IPF file. However, if the user selects "Help/Keys help" inside the Keys Help help window, an error can occur since the help window itself doesn't know about the id in the .IPF file. 2. -or- A better way is: a. Identify the "Keys help" menu with SC_HELPKEYS and specify the MIS_SYSCOMMAND attribute. b. Create an IHelpHandler in your source code c. Have the IHelpHandler object handleEventsFor the main window d. Override the keysHelpId() function to specify the .IPF id for keys help. Example: event.setResult( id-in-IPF-file );  Product information is your own dialog defined in the .CPP.  F1 help for Keys Help must match the id for SC_HELPKEYS. It is defined for OS/2 PM in pmwin.h. It is defined for AIX Motif in ipmrc2X.  You don't have to create a new IHelpWindow object for each secondary frame window you create. However if you don't, the following will occur: 1. When closing the secondary window, the help window doesn't close 2. When closing the help window, focus returns to the owner of the secondary window. Nevertheless, these situations can be overcome by using IHelpWindow::setAssociateWindow() for the current window that has focus. 3. In order to have each notebook page show help when the Help button is pressed, the notebook page must know about the frame window's help window object so that it can call help.show(...). The static function IHelpWindow::helpWindow() returns this object. Example: helpWin = IHelpWindow::helpWindow( this ); if ( helpWin ) helpWin->show( IResourceId( ID_xxx ) ); ═══ 3.6. Toolbar ═══ There are several things we learned about toolbars and toolbar buttons in coding Lancelot. Here are some of the items we learned.  If the toolbar appears in the header definition (.HPP) before the client, the toolbar will take the focus.  Toolbar buttons by default have a standard size. But some words will not fit inside the space provided. Therefore, we allowed each button to size itself based on it's needs.  Since there doesn't exist a standard toolbar button bitmap for some of Lancelot's functions, we created our own bitmaps. These bitmaps must be 20x17 in order to match height and width of the predefined library toolbar button bitmaps. ═══ 3.7. 2D Graphics ═══ There are several things we learned about drawing with 2D graphics in coding Lancelot. Here are some of the items we learned.  If you don't use the setClippingRect() function to explicity set the boundardy of a graphic string (IGString), a graphic string may expand into the area of another graphic string. Then the graphic string can be seen through the other graphic string.