This document is COPYRIGHTED to control its distribution and to prevent it from being used for profit. PLEASE DO NOT DISTRIBUTE IT WITHOUT THIS DISCLAIMER OR THE COPYRIGHT NOTICE. Replication of this document by any means is explicitly encouraged, provided that the content is not altered in any way.
This document is not an official CA product information release. They know nothing of its contents. Well, I hope they know something about the subject, but you know what I mean. It is a voluntary effort by many contributors, co-ordinated by me.
Their respective employers are in no way responsible for the content of this FAQ. Nothing in this FAQ should be regarded as true. Any item of information may become obsolete or out of date at any time. Information about undocumented features should always be regarded as potentially harmful.
The co-ordinator, contributors, and distributors of this document make no representations as to the correctness of the information contained herein, and make no promises to correct any errors or omissions. You use this information at your own peril.
Pat McGibbon pat@megadata.demon.co.uk MegaData Ltd., Dairy Cottage, High Street, MeonStoke Tel: 0(044)1489 877887 Southampton SO32 3NH UK
The following section is concerned with coding practice in OpenROAD. It is largely based on my own experience, and therefore is heavily flavoured with my own opinions on good practice. If your opinions differ, then fine, but I hope there is something of use for most coders buried in there somewhere.
In my opinion, one of the most obvious gaps in OpenROAD is the lack of printing facilities. There are NO built in printing routines either to do screen prints or create formatted reports. There are various 3rd party tools and a couple of unsupported routines to help get round this problem, but until version 4, where I believe the problem is being addressed properly, it's a matter of make do and mend.
Screen Printing
Screen printing can be achieved, via an unsupported DLL available from NAIUA (to members) written by Jon Machtynger, formerly of CA, for MS platforms only.
Report Printing
Various 3rd party tools (see the 3rd party vendors' section.) Ability to call into Report Writer/RBF reports if using Ingres database. Printing can be achieved via DDE to, say, MS Word, using the (again unsupported) interop routines that come packaged with OpenROAD. (On my NT installation, they live in \ingres\w4glsamp\interop in the form of an exported application - this give an example of talking to MS Excel.)
UserEvents get used a lot in OpenROAD, probably too much. There are a number of reasons for this, but most of the reasons given probably no longer hold water (if they ever really did).
Local procedures did not come along until version 3.0. There were thoughts about a very simple type of local procedure in version 2 -- no arguments, no local variables, full access to the caller's local variables -- to allow code reuse, because it could be added very quickly, but it didn't solve enough problem to be worthwhile. The full local procedure support in R3 was a big job that couldn't have begun to fit into the R2 schedule. This was probably the biggest single shortcoming in the product and led to a proliferation of CurFrame.SendUserEvents masquerading as local procedures. There is a fundamental difference in the way UserEvents and procedure calls are handled. Procedure calls are executed immediately, whereas user events (just like any other event) are placed on the event queue, to be executed in their turn, possibly never (depending on the processing of events ahead of them in the queue.)
There is no excuse for continuing to use UserEvents as local procedures in OpenROAD. OpenROAD has proper local procedures, and these should be used whenever a common piece of code is required to be executed 'right now'.
There are one or two specific instances where UserEvents are the right thing to use; for example, to ensure that a piece of initialization code takes place after all field validation has taken place, put it in a userevent block, and send yourself the userevent from the frame's initialization block.
Also, you cannot communicate directly between open frames, (e.g. by trying to execute a call() against the prochandle of a local procedure in the other frame). This needs a senduserevent.
Note that you do not need to send a userevent to change the value of a field on another frame. For example:
fld = frameexec.FieldByFullName(value='o.name'); fld.SetFieldValue(value = 'George the giraffe');This kind of direct synchronous processing is preferable (and more reliable) than UserEvents.
Another common (mis)use of UserEvents is to keep track of opened frames. For example:
Frame a is going to open frame b, or if b is currently open, then bring it to front. But how does a know if b is open or not? One way is as follows (NB NEVER DO THIS)
In frame b:
ON CLICK close_btn, ON TERMINATE = DECLARE ENDDECLARE BEGIN FrameExec(CurFrame.ParentFrame).SendUserEvent('Closing'); END;In frame a:
INITIALIZE = DECLARE FrameBPtr = FrameExec DEFAULT NULL; /* This must be default null */ ENDDECLAE BEGIN END; ON USEREVENT 'Closing' = DECLARE ENDDECLARE BEGIN FrameBPtr = NULL; END; ON CLICK open_frameb_btn = DECLARE ENDDECLARE BEGIN IF FrameBPtr IS NULL /* not yet opened or opened and subsequently closed */ THEN FrameBPtr = OPENFRAME frame_b; ELSE /* Reuse frame b with a bring to front */ ENDIF; END;This is not lovely and gets decidedly less so the more frames that need to be opened. Better is to make use of the undocumented (but it is hidden in the release notes) attribute, FrameExec.WidgetID. It has a value of zero if the frame is closed, and something else if the frame is open. (It is very unwise to rely on the value of WidgetId as being anything more specific than 'something else' for an open frame.)
So now, in the situation above, frame b needs no code at all to tell a its status. In frame a:
INITIALIZE = DECLARE FrameBPtr = FrameExec; /* This must NOT be default null */ ENDDECLAE BEGIN END; ON CLICK open_frameb_btn = DECLARE ENDDECLARE BEGIN IF FrameBPtr.WidgetId = 0 /* not yet opened or opened and subsequently closed */ THEN FrameBPtr = OPENFRAME frame_b; ELSE /* Reuse frame b with a bring to front */ ENDIF; END;
Often it is desirable to have buttons next to fields to which they are functionally related, but this can be difficult to achieve if the field represents the attribute of an object, mapped by a composite field, whose datatype is set to the appropriate user class. The compiler will not allow any named fields in the composite, other than those that match the attribute names.
One solution is to add attributes to the object to cope with the name of the button. This is not really a solution at all. Imagine saying to a DBA, "Could you add a couple of columns to a table for me so I can display some buttons on the screen?". The response would be predictable. The object model should be treated with the same reverence; we don't go adding attributes to objects that the user has defined for us, just for display purposes.
So what to do...? A solution that works quite nicely is to group the field in question in, say, an *unnamed* FlexibleForm, with an *unnamed* button. The compiler can't object because it can't 'see' the unnamed fields. Then, in the code for the top level composite, mapped to the object:
ON CHILDCLICK =
DECLARE
ct = integer not null;
Fld = FormField DEFAULT NULL;
BtnFld = ButtonField DEFAULT NULL;
ENDDECLARE
BEGIN
BtnFld = CurFrame.TriggerField;
/* you can make the find as general or as specific as necessary */
BtnFld.ParentField.ChildFields.Find('ClassName', Value='EntryField', RowNumber=BYREF(ct));
Fld=BtnFld.ParentField.ChildFields[ct];
/* Fld is now pointing at the field next to the button that was clicked */
Fld.DoWhateverYouLike();
END;
Note that from patch 4119 onwards, every class has the ClientData attribute. Very useful in all sorts of ways. Gives the ability to link any two objects.
ClientText can be very useful for holding additional information about a field, including, for instance the name of its validation routine.
(Non MicroSoft platforms)
When loading a TableField with a large number of rows, it may pay you to load the data into an array of the same class first and then duplicate() the array to the TableField array. The reason for this is that if you load directly into the TableField, there can be a noticeable delay at the end of the select where the TableField scroll bar seems to be flickering for a number of seconds. This is because that every time a row is loaded into the TableField an event is queued to resize the scrollbar. At the end of the select loop (or whatever) the system then processes all nine zillion queued events and you get to sit and watch. If you load direct into an array and then duplicate() across, you only get the one resize event.
The ChoiceList class can be very useful for storing fast lookup lists by making use of its built in search methods: IndexByText, TextByValue, etc. These lookup searches are faster than the more generally useful Find method available for all arrays, and allow you to hold lookups of just about anything: EnumText, EnumValue, EnumBitMap, ClientData attributes are available for every row of the ChoiceList's ChoiceItems array. These are very nice. Recommended!
QueryObjects are not easy to use and indeed were not really designed for public consumption - they were going to be part of the Architect product - but they do pay dividends once mastered. They allow the data access layer of the application to be designed rather than coded on the fly; no more ever spreading lumps of SQL littered about the code.
The main problem with QueryObjects is probably the lack of documentation. The section on QO's in TFM is sketchy at best. It was done at the time of the take-over and maybe the writer's mind was concentrated on more pressing matters. This means that it is not easy to see which are the important attributes to use - some of them are redundant - and in what order they need to be set - yes, it matters.
Having worked out which attributes matter, the setting of these attributes is a laborious matter requiring many lines of awkward code, which lends itself to typing errors. We then come to the second major drawback in using QueryObjects; the error feedback is almost non-existent. Basically, you get told something is wrong - if you are lucky; they can fail silently, as well - and are left to figure out the rest for yourself.
There is a crying need for some sort of QueryObject painter, which allows you to see what need to be set and what their current values are. Imagine if you had to dynamically create all your frames, instead of using the Frame Editor; do I need to say any more...?
If I ever finish writing this FAQ, I intend to produce a QO painter and make it available through the NAIUA Tool archives. Maybe, I will even have it ready before OR 5.
The point is that QueryObjects are actually worth the grief and a lot of real life OpenROAD projects are taking the trouble to use them because the benefits definitely outweigh the drawbacks.
While we are awaiting the arrival of the QO Painter, here are a few points to look out for:
IF QO.Status = QO_ACTIVE THEN QO.Close(); ENDIF;
QO.Scope = CurFrame.Scope; /* not too bad */ QO.Scope = CurMethod.Parent.Scope; /* now where did I declare that array...? */
OpenROAD comes with a whole load of useful field templates built in. They would have been even more useful with the source code, but I guess you can't have everything. It is very easy to develop your own field templates and can pay rich dividends. Here are a few examples.
Password field template
Although OpenROAD doesn't supply 'Password' type fields that echo *'s for characters typed, it is easy enough to set up a field template to do the job for you. Again, when I get a minute I'll post an example to the NAIAU Tools archive.
Dropdownedit field template
The principles used in the password field template can be used to simulate a searchable drop down list, where as the user types into an EntryField, the search homes in on the appropriate entry in the list.
Both templates rely on a SendUserEvent loop which every time it is activated sends itself a delayed user event and checks what the user has typed. In the case of the password template, it adjusts the number of displayed *'s; in the case of the drop down edit field, it finds the appropriate place in the list. The loop is broken by purging the user event when the user leaves the field.
Yes, I will post an example when I get time.
Standard selection lists should not be hard coded in every frame where they appear. What happens when the list changes? Are you sure you know every frame in which the list appears. One approach is to have a globally accessible frame where the lists are coded and then they are duplicated in every frame that needs them. The original list can be hard coded or data driven, but either way you achieve single point of change.
Often there may be several triggers for a similar action in a frame. E.g., the close action will be triggered by a menu button, a button on the screen, a toolbar icon, etc. The close action checks for unsaved changes before it returns, which code you do not want to have to duplicate.
One way of avoiding the code repetition is to 'piggyback' the triggers on to on event block:
ON CLICK close_btn, ON CLICK menu.file.close_btn, ON CLICK toolbar.close_btn = DECLARE ENDDECLARE BEGIN /* Check unsaved changes and if OK then close */ END;
This might seem an eminently sensible way of approaching the problem, but in fact is a slippery slope, paved with good intentions, on the road to nowhere (holy mixed metaphor). The problem lies in the fact that as soon as my user tells me that they no longer like the toolbar, and I obligingly remove it, my code no longer compiles. I have a horrible cross dependency between my code structure and the objects that happen to be on the screen. Ugh!
But obviously I do not want to recode the same block several times, so I parcel it up into a frame level local procedure and put a call to it behind each of the relevant 'close action' objects. Thenceforth, I can add and delete such object to the frame as the user sees fit without having to touch already stable code.
To put in comments that won't be even picked up by the compiler:
For Blocks of text:
#if 0 loads of stuff in here that won't even show in the processed script #endif
For single lines:
#-- this is not documented but it works
Ever wondered how to find the calling frame/procedure from a procedure or method? You can't use ParentFrame, because it's not defined at this level. The undocumented Parent attribute does the same job and works for ProcExecs and MethodExecs as well.
Use it at your own risk.
None, except for the manuals that come with the product. Available from CA to holders of any CA product licence, but not otherwise.
The only books that I know of pertaining to OpenROAD are those sold by Computer Associates. There are 4 main manuals: System Reference Summary, Programming Guide, Language Reference Manual, and the Application Editors User's Guide. Remembering a debate here on this newsgroup (comp.databases.ingres) several months ago about whether individuals with no Ingres licenses could purchase these documents, I called CA's document ordering number (1-800-841-8743 in the US) to get their story. I was told that as long as you have ANY sort of CA license (doesn't have to be an Ingres product) you can purchase the above-mentioned set of manuals for $50 (that figure sounds low, but hey, that's what I was told). If you don't have ANY CA licenses, you are out of luck, they are not allowed to sell you any of their documents.
I think this documentation issue has gotten unnecessarily murky here! OpenROAD version 3.0 came in a big box with printed manuals including the "Language Reference Manual," the "Application Editor User Guide," and so on. These are nice to have around in paper form, but they are not available for OpenROAD 3.5 and beyond. In 3.5, the current version, CA reorganised the documentation to put a lot into on-line help files, accessible from inside the development environment. Additionally, however, they "reprinted" the old manuals more or less in full (and without much new material), also in on-line format. In the MS-Windows distribution, all of the old manuals are available on-line, and you reach them by clicking the "on-line documentation" icon in the OpenROAD development folder.
So there's more documentation on-line than what you get in the help files, if you know where to go for it.
I don't know how they did the on-line documentation for the UNIX distributions, but I know for a fact that they did not do paper documentation for 3.5.
Basically, I didn't want anyone to get his or her hopes up too much--the documentation, on-line or not, is really not what it could be, and it would indeed be nice if some kind soul would write an OpenROAD book to flesh things out a bit more.
The only way to handle strings above the varchar limit of 2000 bytes is to use StringObjects and their associated methods for filehandling and database handling. They are well documented in TFM.
Use BitMapObjects; they work a lot like StringObjects. Again, RTFM.
Information on the latest patches and the bugs that they fix can be obtained from CA Tech Support.
Patches for OpenROAD can also be obtained via anonymous ftp from mf.cai.com
When I last went to look (2nd September 97), the appropriate directory for me was: /CAproducts/ingres/int_wnt/OpenROAD/3.5_02_00/Ingres/p4796/ but YMMV.
At the time of writing OpenROAD 4 is due any day now. Information is available from CA at (http://www.cai.com/products/ingres.htm) and from NAIUA at http://www.naiua.org/or4_0.html
Always.
It can take a bit longer to get into the swing of writing OO code but it does provide some big pay-offs.
To write your OpenROAD code as OO you have to know what your objects are. To do this you have to talk to your users and find out what their objects are. (I know this smacks of heresy, but bear with me.) Once you know what the users objects are and how they use them you should be talking about things the user recognizes. The bottom line is that if the user knows what you are talking about, then it is an object (and so needs a User Class) and if they don't, it isn't (and so doesn't). Having created the user classes as the user's objects with the attributes they describe to you, the methods come out of the user's descriptions of what they do with the objects. Note that we have not even thought about coding a single frame yet, and we have the major structure of the application already in place! This is one of the reasons, it can be hard to sell this approach to a lot of managers. 'Yes, but where are my screens...?'
Of course this will be bound up in one of the standard OO methodologies, but that is what it boils down to. Couple this approach with strong standards enforced through frame templates and field templates, and you will end up with an application that is far more likely to meet users' expetations, present and future, behaves in a consistent fashion and is far easier to maintaint than the spaghetti code alternative.
HTMLised by Don Simonetta