PowerBuilder Tips

As an instructor and course developer for many years over engagements for several employers, I developed proficiency in AREV, Powerbuilder, HTML, Sybase, Unix (AIX) and Oracle, among other technologies and products. As a diligent supportive instructor,  I took it upon myself to write short notes and handouts for students. The notes sought to firm up understanding of concepts covered that day, give more examples of topics, or reinforce a novel concept or powerful feature we had seen that class day.

A representative selection of my content of this type is provided here, to give evidence of my written style, and depth of technical understanding. Contact me if interested in my services. I am very good at knocking out technical and end-user oriented documentation. 

Twitter
LinkedIn

  Oracle Tips

  Miscellaneous Client/Server, IDE and Unix Tips

Dealing with checkin failures
You may occasionally experience

     'Check in failed.  Possible library file I/O error'

messages when trying to check in.  The easiest thing to do is just persist.  If you cannot save the change back to the home library, you can use the Source menu to clear check out status and use the Library Move operation (or copy, if you prefer) to get the changed version from your checkout library to its proper final home.

Parent vs. ancestor
In the Powerscript language, 'Parent' is a reference to the instance of the object on which the current object resides (a 'container' relationship). In OO parlance, 'ancestor' refers to the object class from which your current object is inherited. 'Parent' is an addressable 'object pronoun', whereas the term 'ancestor' is just a concept, and not an addressable keyword. I have seen the two terms mixed too frequently, unfortunately, which makes learning more difficult than needs to be the case

The ANY Datatype
Similar to the old Visual Basic data type of 'variant', the 'any' datatype of PowerBuilder is a very forgiving 'inbasket' for data coming from an unpredictable source, such as an OLE call to a spreadsheet cell.  Creating a 'chameleon' variable of type any lets you receive that incoming data and the variable's datatype is recast upon use. Here is a fragment of the help file from PowerBuilder; note the use of ClassName(), rather than TypeOf() to test the datatype:

any la_x

la_x = ole_1.Object.cells(1,1).value
CHOOSE CASE ClassName(la_spreadsheetdata)
CASE "integer"
...
CASE "string"
...
END CHOOSE

Editing, and undo/reset behavior in a DataWindow edit control
When the user interfaces with a DataWindow object residing within a DataWindow control, they are always, (and only) interacting with a floating editing feature called the Edit Window.  This is what supports such editing keystrokes as backspace for deletion, cursor movement within existing text, and the ability to undo changes by pressing {Esc} (unless the Cancel property for an exit/cancel command button is set). 

Another possibility for 'undo' is the GetItemXXX series, which supports the optional switch of getting original value for a column, as:
   dwcontrol.GetItemString( rowvar, colvar {, dwbuffer, originalvalue })
This last switch relates to the pseudo 'original' buffer in the datawindow. 

You may also want to know about the Undo() function, for DataWindow, EditMask, MultiLineEdit, RichTextEdit, and SingleLineEdit controls.  There is a CanUndo() function which can be tested to see if an undo is still available.

(Perhaps this next one is a reach, but you may also want to know about the ReselectRow() function, which will let you 'refresh' a DataWindow row from the database.)

The next resort is to not send the SQL to the backend, which, as you remember, is the purpose of the builtin Update() function of the DataWindow.  This generates the appropriate DML (Insert, Update, Delete) based on the status of the row and column buffers of the DW.  Talk about encapsulation!

Finally, the LAST resort is on the level of the database server itself, which must choose to do a COMMIT before the changes are actually applied and the relevant resources unlocked.  If you do a ROLLBACK, you will undo the changes which were attempted, but no distinction is made between the changes you might have made, and those made to other parts of the database, such as trace or audit trail files.

Variables which hold object references
A very powerful option to PowerBuilder programmers is the ability to address other classes at runtime. This network-wide addressability, using proxy objects, is part of CORBA-compliant distributed PowerBuilder and Jaguar CTS. (see DataWindow synchronization). The 'instantiation' of a working copy of a class requires the declaration of a local/instance/shared/global variable of that class type, as: l_service_object u_service_object02. From that point on, the actual remaining syntax will vary. User Objects, such as the one cited before, would have a formal CREATE statement, as:

   l_service_object = CREATE u_service_object02

Structures are instantiated by their mere citation in your code, (i.e., no "CREATE"). Window instances are easy: use any variant of the OPEN function.

Recipe for Polymorphic Menus
We try to use a minimum number of menus, often needing only one menu per module, rather than one per window, as is often the case.  This simpler design is possible using a simple recipe (once you understand the syntax of these features): 1.) Code, using inheritance, a generic ue_save or we_save event, in your Window class 2.) In the menu clicked event for an abstract event dedicated to saving (updating) data in a DataWindow within a Window, code this. 
   Window lwin /* or some more specifically cast reference,
                            when more specific functionality required */

   lwin = w_main.GetActiveSheet
   If IsValid( lwin ) Then lwin.TriggerEvent("ue_save")

Be careful with the Application Object
The Application object is the entry point to the Powerbuilder application. There are 6 events in the application object.  Two of them have misleading names: connectionbegin and connectionend.  These have nothing to do with database connections, but rather to connecting with an app server written in PowerBuilder running as a separate process somewhere on the network.  Such app servers can centralize business rules and do 'middleware' type work, such as load balancing using alternate database connections. I was using the Open event of the App Object to create a number of custom NVO's, which in turn had posted events in them.  I had a lot of problems with this architecture.  I subsequently moved almost all the processing to the Open event of my MDI frame window, and the problems cleared up.

I have also had trouble with importing the modified source of an app object (in the Library Painter), and with trying to do Check Out on this object.

To conclude --- try not to mess with this!

Relationship of a database connection to DataWindow Painter
When you try to paint an existing DataWindow, you may think you need to have the actual table(s) that the DataWindow accesses at runtime.  This is not true, and can actually be quite handy, if you are trying to spruce up the visual layout of the DataWindow, and modify other characteristics and parts of the DataWindow, such as bands, conditional attributes, and the like.

The only thing you need to avoid is trying to modify the Data Source.  If you try to do that, the actual table(s), and the correct column names of same, must be present in the database. 

DataWindow Painter Tip
When you are using the DataWindow painter and trying to move things around with the mouse, it's all too easy to click on something and inadvertently move it. What I have found to be a good habit is to immediately use keyboard sequence {Ctrl} Z to 'undo' the last change. This is useful because I don't have to use the mouse again to try to reposition the object, and I am guaranteed that the previous position is restored. Again, you need to become habituated to this approach, but the amount of frustration which might accumulate by using the other approach should finally prove persuasive.

Limitations of the PowerBuilder Debugger
Don't try to use the debugger to trace through to a script which has been invoked through posting (i.e., PostEvent or its more modern equivalent).  The debugger will get lost.  Posting to methods can be appealing, but making synchronous calls to scripts using functions does provide better control over the flow of processing, and also works fine in the debugger.  You probably also know ( and certainly should know ) that focus-related events cannot be debugged using the debugger, since it contends with the object script for focus.  That is why an alternate approach is warranted, using FileOpen, FileWrite and related methods in a 'trace' or 'log' function, which can be run from any script.  Such an approach also pays off when you have deployed, and have runtime problems with the application.  You could also consider temporarily installing an extra listbox or external source datawindow to which you 'write' your trace/debug information, showing 'processing got this far' and 'variable contains this value' type of information.

Object Browser
One of the benefits of the object browser is that, for large applications with lots of PBL's, you can find an object alphabetically in the browser, then right click on it to edit it. This seems to be the fastest way to find an object (as long as you know its name, or at least would recognize it).

How to announce non ANSI-compliant syntax in Sybase
Use the fipsflagger setting of Sybase to get acquainted with which parts of a Sybase application can be ported to other RDBMS. Here is an isql session illustrating its import of fipsflagger:

1> set fipsflagger on
2> select db_name()
go

Line number 2 contains Non-ANSI text. The error is caused due to the use of
Sybase built-in function: db_name.

Other errors will occur when you use Sybase's T-SQL variables.  For example, a batch may cause this error:  Line n contains Non-ANSI text. The error is caused due to the use of
Sybase declared global/local variables.

Here's another:
use pubs2 will cause:
    SQL statement on line number 2 contains Non-ANSI text.

The error is caused due to the use of USE DATABASE.
 
PowerBuilder and Transactions on the Server
If you are not aware of the differences, and if you use a lot of the PowerBuilder training materials out there, you may entirely miss the very important differences in how your PB app is actually handling transactions.The functionality pioneered by Sybase, and incorporated into the Microsoft product, supports the use of 'Chained' or 'Unchained' mode of transaction handling. The Sybase default, the 'unchained' mode of transaction handling, is that EVERY DML statement is an atomic transaction.  In order to batch several statements which must stand or fall as a LUW, you need to use the BEGIN TRANsaction statement. The ANSI standard is that the server automatically and silently sends an effective Sybase-style 'BEGIN TRAN', and only requires (and supports) the COMMIT or ROLLBACK syntax.   The PowerBuilder training materials I have seen only refer to COMMIT and ROLLBACK in relation to transactions, and are therefore ANSI-compliant syntax, but those who are running Sybase or MS SQL Server must ensure that the 'chained' mode of operation is appropriate to the syntax in their PowerBuilder scripts, or vice-versa. It would be nice to just include conditional syntax in your PB scripts to test for whether chained mode is available or not, but you must be able to connect to such a server during compile time, which makes things a bit more problematic for those who don't have such connectivity.

Getting the DW Painter to recognize complex result sets in Sybase
A colleague of mine was having trouble with a Sybase stored procedure which had a lot of if constructs.  Even though the sp ran fine thru ISQL and the PW ISQL window, the DataWIndow would not build.  I recommended creating an extra SELECT query at the top of the routine which had a WHERE clause which was always false, and armed with this 'extra' result set, we could build the DataWindow.  We then subsequently modified the sp and all was copacetic.

Using the Repository for Dynamic DataWindows
To have more complete control over the starting properties of a DataWindow you create dynamically, be sure to populate the PB Repository table named 'pbcatcol' with custom values in the pbc_hght, pbc_wdth, pbc_mask, pbc_edit and others.

PowerBuilder 7 and the Web
The only direct relationship between PowerBuilder and the Web is the ability to leverage your understanding of DataWindows.  Using a Design Time Control (DTC), developers can generate a DataWindow which at run time does its powerful retrieve thing, with retrieval arguments, computed columns on server and on the DataWindow, and supports drop downs and expressions. At runtime, if you use PowerDynamo, you will then be able to create a ton of Javascript automatically.  You will also be able to take PowerBuilder NVO's  and drop them in the new Enterprise Application Server, original nickname Jaguar, and make your business logic available to anyone in the world with internet, CORBA or DCOM connectivity.  In any event, think NVO!

How Polymorphism and Menus work
If you are creating an MDI application, you must either create a menu for every sheet, or use polymorphism to make calls to the currently active sheet, either through inheritance or by having one menu per module or application.  As a novice, your best bet is to at least use inheritance to have a common look and feel for the menus, and to assign a specific menu to each window.

To have a polymorphic menu, have its scripts interrogate the MDI frame with GetActiveSheet, return a reference from there to the active window, then use TriggerEvent() to send a generic message, as TriggerEvent("we_save").

Using the Repository for Dynamic DataWindows
To have more complete control over the starting properties of a DataWindow you create dynamically, be sure to populate the PB Repository table named 'pbcatcol' with custom values in the pbc_hght, pbc_wdth, pbc_mask, pbc_edit and others.

Library painter tip for 'straddling' multiple folders, drives
If you have PBL's in your Library List which are on more than one drive, be sure to take advantage of a feature which started with PB 7.0, namely the ability to right click in the left-hand pane of the library painter, choose Set Root, then choose Library List.  This puts all of your PBL's, no matter how scattered, into a uniform view, making copy/move and other maintenance operations easier.

'Sneaky connect' behavior of Data Window Painter
When you preview a Data Window in the builder, you get a whole world of functionality which you would otherwise have to program. Normally a transaction object must be populated and used in a CONNECT operation. Then the SetTransObject() method is used to associate the runtime rereference of the connection with the DataWindow object. Finally, the Retrieve() and Update(), plus COMMIT/ROLLBACK operations and decisions are needed to finish the effect.

A 'quick and dirty' Entity-Relationship viewer/editor
If you are building a query or stored procedure and want to visualize the relationships between tables of interest, and you don't have ERwin, Rational Rose, Embarcadero or some other modeling tool available, access the Database Painter of PB and open the tables in question into the display area/workspace.  You right-click on the table in the 'objects' panel, then choose 'add to layout'. 

Graphic depictions of the RI links between tables, if any, will automatically appear; you can also, of course add PK-FK relationships from the Database Painter.  Although this diagram can't be directly printed, I usually use the {PrintScrn} key to put the image in the clipboard, then print it from there via Word, WordPad, or Paint.

Finding the stored procedure data source in the DW Painter
If you are maintaining a DataWindow object which is based on a stored procedure, and you want to see the sp name and calling syntax, you need to right click on the column definitions at the bottom of the screen.

Maintaining a running status display in a Window
In addition to the Microhelp feature, you can inform users of a long-running process how things are going by using either a ListBox or a DataWindow (external).  As you progress through the various steps of the process, either use AddItem() or InsertRow() to add a new line of info to the display.  One advantage of the DataWindow is its inherent print capabilties, which is good if you put the date and time, and maybe timings of how long the steps of the process runs, in the DataWindow.  If you use a Listbox, remember to turn off sorting!!

'Trust Me' complilation for references to objects which don't yet exist
If you have to refer, within one script, to a function which has not yet been coded in another object, or elsewhere within the current object, you have a way to satisfy the compiler and 'get on with your life'.  Assume you need to (finally) have a function named uf_return_rate() in an object named u_business_obj, to which you will refer via a global reference variable named, say g_bus.  The function, again,  has not yet been defined, not even its signature.  To get the current script to compile, use:

        g_bus.TRIGGER DYNAMIC FUNCTION uf_return_rate()

Because of the inefficiency of this method call, you should always go back and clean this code up, once the function in question has actually been created in the other object.

Java Notes for PowerBuilder Developers - OO compliance
Both Java and PowerBuilder base their architecture on inheritance, 'right out of the box'. For example, every Java object is finally inherited from java.lang.Object, and the PowerObject which sits at the top of every PB family tree. Both of the 'ultimate ancestors' support some basic functionality, but not much, in keeping with good OO design principles.

In Java, the term 'ancestor' will not register with most. Rather, you use the term 'superclass', and the descendant class is then called a 'subclass'. The this keyword is similar. By the way, PB is considered 'object based' because it derives all objects you can create from built-in class PowerObject. Java is truly OO, just like its counterpart, C++.

Whenever you inherit from a superclass in Java with the 'extends' keyword, you are specifying your direct ancestor (I mean 'superclass'). Like PB, Java only supports one direct ancestor/superclass. What java provides is the additional ability to use as many 'interfaces' as you need, to stipulate your ability to implement functionality specified in the interface). This 'implements' syntax lets you guarantee a large chunk of functionality so that you can amass some significant functionality in short order. In Java, you will need to use the SUPER keyword to cause the superclass code in the same method to run. This is implicit in PowerBuilder, so remember to make the adjustment.

Easier addressing of embedded controls

Because a Control on a tab page, such as a DataWindow or command button, must normally be addressed using at least two levels of dot notation, as 'tab_1.tabpage_3.dw_master', I often find it useful to create instance variables of the appropriate type (i.e., DataWindow or any subclassed version, CommandButton, etc.) and set references to those nested controls as the Window is initializing.  Seems a lot cleaner, but of course the nesting is not as apparent in scripts.

Thoughts on Jaguar-PowerBuilder integration
PowerBuilder DataWindows can be used in n-tier applications quite easily if you use Jaguar, and DataWindow synchronization.  What you do is use non-visual objects to sit on the Jaguar server as components.  Those components will have no visual characteristics at all (i.e. they will not be built with inheritance from any visual PowerBuilder class, such as Edit and Window), but will have instance variables which refer to one or more datastores.  You would connect to the database from the Jaguar-based component, and let the application server worry about database transactions, and optimizing use of backend connections.  You provide a public set of function calls in those components, from where you pass by reference a dataset from that component-based datastore to the DataWindow on the 'surface'.

(Related note: if you are using the PFC, you need to override several methods in the DataWindow class which look for a transaction object.  You will not be using a database connection from the client anymore, so rethink that, and just focus on function calls to get the data, with SetFullState() and GetFullState() methods on the NVO.)

Application Object events
Of the 6 events in the application object. 2 of them have misleading names: 
     connectionbegin and connectionend

These have nothing to do with database connections, but rather to connecting with an app server written in PowerBuilder running as a separate process somewhere on the network.  Such app servers can centralize business rules and do 'middleware' type work, such as load balancing using alternate database connections.

Building good validation into DataWindows
There are two common areas of misunderstanding when trying to build validation rules.  One of them is the requirement to use the GetText() function to interrogate user input.  This is the ONLY way to tell what they have typed; column name references refer to original (pre-input) contents if any.  Since the input is always passed by PowerBuilder as a string, use the data conversion functions to permit date arithmetic, math, etc.  The second common mistake is to misinterpret what the 'Invalid Expression' message means in that area of PowerBuilder.  This message can refer to either the validation rule OR the custom error message you may choose to assign.  Custom error messages MUST BE IN QUOTES!

Relationship of a database connection to DataWindow Painter
When you try to paint an existing DataWindow, you may think you need to have the actual table(s) that the DataWindow accesses at runtime. This is not true, and can actually be quite handy, if you are trying to spruce up the visual layout of the DataWindow, and modify other characteristics and parts of the DataWindow, such as bands, conditional attributes, and the like. The only thing you need to avoid is trying to modify the Data Source. If you try to do that, the actual table(s), and the correct column names of same, must be present in the database.

A more certain way to control when scripts run
If you need to have a called script run to completion before the next statement in the calling program is executed, put the first script into a function, and have the second test for the returned result of the called script. This is the easiest way to have your programs 'take turns' and run in a predictable order.

Client or Server --- Who should do what?
It is possible to perform the same work on either the client or the server in a client-server installation.  Those operations relating to sorting or filtering can be done in the PowerBuilder DataWindow with SetSort(), Sort(), SetFilter() and Filter(), but this should  be done on the server, since it can be optimized to do this work much more easily than the PowerBuilder client application.  That does not mean that you cannot give the user additional filtering and sorting capabilities in the client app once retrieval has been done.

It is also important to recognize the value of using stored procedures whenever they are available, since the query is then compiled into an executable and stored on the server, and is often stored in cache memory.  PowerBuilder applications can invoke the stored procedure and return a result set; the speed advantage over interactive queries grows as the database grows.

Changing the ancestry of existing PowerBuilder Objects
In the early days of 'growing' a class library, you may not know where in an inheritance scheme a particular window or user object may finally go.  What we have found very useful is to always create new objects with inheritance, but to deliberately inherit from very abstract placeholder objects, whose purpose in life is to, literally, be 'placeholders' in anticipation of later reassignment of the descendant.  Once we have matured that area of the architecture to understand where a given user object or window should belong, we 'retro-fit' that object by exporting its source using the Library Painter, then doing a search and replace of the 'placeholder' name with the desired 'true' ancestor.  Works like a charm unless you fail to inherit in the first place.  Windows inherited from type 'Window' (the default) cannot be reassigned.

Conditional Expressions in DataWindow Columns
You can conditionally hide, protect, move and otherwise govern the look and feel of a DataWindow column without resort to programming, by right clicking on the column in the Painter, then choosing 'Expressions'.  The test conditions of note are the IsRowModified() and IsRowNew() functions, to hide/show a bitmap or colored drawing object, or to protect any key columns if the Row is not new, etc.  Remember to double click to launch a friendly painter for your expressions.

Java Notes for PowerBuilder Developers - Messaging
Messaging between object instances is what you are used to with PowerBuilder, except that PostEvent style processing is not supported.   Note also the Java Messaging Service API (JMS), which will support asynchronous messaging over a distributed environment.

As with PowerBuilder, you can pass reference to rich objects through variables of that 'type'.  There is a 'new' keyword which instantiates an object and also runs its constructor method, which can be overloaded on the arguments portion of the signature.

The Java world supports 'callback' methods, so that EJB's, for example, being forced to implement interfaces which make it addressable, has callback methods so that the container / server can communicate with them.  You can do this with PowerBuilder; it's just that the EJB container chooses to do the messaging, and it's out of your control in EJBs.

Java Notes for PowerBuilder Developers - Instantiation
Both Java and PowerBuilder support what is called a 'factory method', whereby a runtime reference is generated dynamically.  The PB CREATE USING {String} syntax has its counterpoint in Java.  See, for example, the EBJObject and the 'home' in EJBs.

When an instance is being created in Java, you can take advantage of the requirement that every class being instantiated will have its own constructor event (in PB parlance), in the sense that if you do not explicitly code a constructor, the runtime environment creates an empty executable version of the constructor during instantiation.

Use overloading to permit you to pass arguments to the class during the 'new' invocation of an instance.

(By the way:   In Java, a 'constructor' is not an event with that name; rather, it is a function (method, to be more correct) which is given the same (case sensitive)  name as the class which it services. 

Thus, for a class named MyClass, there should (and finally, will)  exist a method with the same name.  More than one version of the constructor method may be created, using different argument lists.  For example, the String class can be fed a content during its instantiation .

Checkout and Connect

Library checkout is easy once you remember to CONNECT to the source control regime for your current installation. If the Check in / Check out choices are greyed out in the popmenu, choose Source then Connect from the Library toolbar.  As with previous versions, you must provide a name which is associated with the checkout.  This is a freeform label, and has no relationship to any other network or application or database names.

Powered by WebRing.