Suggested PFC Extensions
As indicated by PowerSoft itself, few (if any) applications will use PFC "right
out of the box". PFC provides a very good base on which to build but it does not
include all possible generic and reusable code that an application might need. For this
reason, any project needs an extension layer to provide additional generic capabilities as
well as additional capabilities that are specific to the application being developed.
General strategies and architecture advice for extending PFC
are provided on this site. This page deals with specific functional extensions.
Purpose
The purpose of this page is to provide a list of PFC extensions that you might consider
when architecting (or extending) a PFC-based infrastructure. Keep in mind that this is a
list of suggestions and some may be applicable to your organization while others may not
apply.
PFC Extension
Functionality to Consider
Typical extensions that could be considered for PFC are broken into the following
categories:
New Visual Objects
PFC provides a number of generic "visual" objects intended to serve as
ancestors for application windows (sheet window, response window, command button).
However, with a few notable exceptions (e.g. Logon window, generic Sort window), PFC
doesn't provide visual ancestor application windows. PFC's decision not to supply visual
ancestors is largely understandable -- there are many ways to build an applications and
many philosophies about what is better and worse. PFC doesn't want to try to impose a
particular GUI design on the many end-applications that use the framework.
When creating your own extensions to PFC, you don't have the luxury of not
"commiting yourself" to any particular GUI design philosophy. There are several
good reasons for creating visual ancestors which impose a GUI design on your PFC extension
layer:
- visual ancestors will tend to standardize the user interface for PowerBuilder
applications. This will make it easier for users to move amongst various PowerBuilder
applications.
- visual ancestors will reduce the learning curve for developers moving amongst various
PFC-based applications in the company
- visual ancestors will reduce development effort and they will standardize the
functions/events that each descendant codes (the standard functions will be defined by the
ancestor).
A typical project could consider the following standardized visual objects ancestor
windows (i.e. extensions to PFC):
Each of these visual ancestors is below. In addition, you can find specific suggestions for defining visual
ancestors.
Search / Results of Search
Window(s)
Most business applications need to capability to search for an object,
based on predefined search criteria and display the rows that match the criteria. This
search ability generally needs to be built for every major business object (with different
criteria and results of search criteria for each object).
If you are not using PowerBuilder's standard Query By Example (QBE)
capability, then three broad visual approaches might be used to provide searching
capability:
| Format |
Advantages |
Disadvantages |
Format 1: one window with one list DW.
Allow the user to enter the search criteria right into the
"results of search" list. Matching data is displayed in the same list. |
- simple window appearance |
- only data that is displayed can be used to
search
- search criteria "disappear" when search is invoked |
Format 2: one window with an enterable DW and
a list DW.
Allow the user to enter search criteria in a separate DataWindow at
the top of the window and display results below. |
- the user can see search criteria and results
in one place |
- if there are many search criteria / search
options, there may be little room to see the results |
Format 3: two windows: one for criteria and
other for results
Have a pop-up response window where the users enters search
criteria. After the search is initiated (user hits OK), then the response window
disappears and the application display the results in a "results of search" list
sheet. |
- lots of space to enter search criteria (on
pop-up window) and a window-full of results are listed
- with ample space, it can handle the most general searching requirements
- pop-up search criteria window can be reused with Lookup window (below) |
- more complex interface (two windows) |
Generally speaking, it is advisable to select a single search approach
and use it consistently throughout the application. This will provide more consistency for
users and it will require less code be written and maintained by the team.
Some standard functionality that can be included in the search (or
search/list) window ancestor(s):
- call to a standard validation function after criteria is entered and
before search starts (e.g. to verify mandatory search criteria was specified)
- ability to clear search criteria
- ability to recall several generations of criteria
- ability to handle "no rows found" situation (e.g. redisplay
last search criteria)
- ability to format and display the search criteria on a line at the bottom
of the list window
- the list window can automatically initiate the creation of new object and
the opening of existing object in the list (when the object's corresponding detail window
name is supplied by descendant)
- ability to automatically open the object, when exactly one row is
returned
- ability to restore original sort and filter settings, each time the user
starts a new search
PFC functions (like DataWindow sorting, filtering and row selection) can
be invoked to help the user manipulate the results of search list.
Lookup Window
What is a Lookup Window
When entering Detail data, users are accustomed to being provided with a
drop down list (or drop down datawindow) which lists the possible values for the column
they are entering. Unfortunately, when there are more than 50 possible values, the
dropdown list is a poor display mechanism since it forces the user to do a
"linear" search of the data.
Consider an Order Entry window where the user has to specify the
customer. There may be 100,000 possible customers. Clearly, the user needs the ability to
enter search criteria to limit the data displayed and resorting of results could help too.
In old-fashioned systems, the user would have had to write down the customer number and
type it into the Order Entry window.
In a "good" Order Entry window, the user would press the
dropdown arrow in the Customer column to find the Customer (alternatively, the user might
press right-mouse and choose a Lookup right mouse menu item). A modal (response) Lookup
window would then be presented where the user could enter criteria (e.g. customer name,
phone number, etc.). The matching customers would be displayed in a list. From the list,
the user would select the correct customer and the resulting Customer Id would be returned
to the Order Entry window.
Lookup Window Capabilities
The capabilities provided by the Lookup window are very similar to that
of the Search / Results of Search list window. However, there are differences. In an MDI
application, the results of search list would be a sheet while the Lookup is more suitably
a response (or modal) window. Also, a number of non-essential actions (e.g. printing,
opening a detail, creating a new object, etc.) would be excluded from the Lookup since
these operations have nothing to do with finding the object.
Tree Window
PFC provides a fairly comprehensive treeview ancestor/service. However,
there can be several useful capabilities implemented in a Tree List or Tree Detail
ancestor:
- an application might require standard interaction between a treeview
control and an associated Detail DataWindow. The details for "current" treeview
item might be displayed beside the treeview and the user might be allowed to edit those
details
- the user might be able to search for items in the treeview -- and the
treeview might open to show the first (or all) occurrences of the items that match the
specified criteria
Depending on how a tree is used in your application, you might find it
useful to build an ancestor Tree Window to support your specific requirements.
Detail Window
A Detail window is a window that shows the details of an object in one
or more DataWindows. Standard Detail Window functionality could include:
- providing a hook for window-level validation
- provide hooks for window-level pre-save and post-save logic
- passing along menu commands (e.g. add row, delete row) to the list
DataWindow
- providing improved DBError handling with custom messages (in the window
pfc_DbError event)
- overriding default PFC CloseQuery logic to overcome a weakness (see tip)
- providing a standard facility to gracefully shutdown the window (see tip)
Notes:
- much of this detail window logic can be placed into w_master so it is
available to a wider variety of windows (including the Tab Detail window below).
- In addition to providing validation, pre-save and post-save hooks at the
window level, standard DataWindow level functions could be invoked during each save
operation (for DataWindow-level validation, pre-saving, post-saving logic). PFC6 provides
this kind of ability with its self-updating updates.
Tab Detail Window
The window described in this section assumes that a large object is
being maintained through use of a tab object (which contains several tabpages).
Standard layering for a tab window in PFC is as follows:
- DataWindows objects are built
- TabPages user objects (inherited from u_tabpg) are built and various DW's
(inside their u_dw container) are placed onto the tabpage
- Tab Objects (inherited from u_tab) are built and various TabPages are
inserted onto the tab object
- Windows are built and a Tab (usually only one) is placed on the window
It is good from an encapsulation perspective to build the Tab Detail
Windows in four layers like this. Tabpages are built independently of each other and the
Window doesn't get overly complex as a result.
The challenge is that much standard communication is needed between the
layers, such as:
1. The tabpages and DataWindows might need to obtain
the following information from the Window-level:
- the primary key: this is used for retrieving (and possibly re-retrieving)
their data
- object indicator: specifies if the object is "New" or has
already been saved to the database
- object status: indicates the current status of the object (e.g. Active,
Inactive or Approved, Pending, etc.)
Note: this information was probably obtained during Window opening but
it might change as the user performs actions. The TabPage probably needs to call standard
Window functions to access the "latest" values.
2. The Window needs to tell (or ask) the Tabpages and
DataWindows about the following:
- open: window is opening so retrieve your data / prepare yourself, if
appropriate
- validation: is your validation OK (I'm about to save)
- pre-save: I'm about to save here is the standardized timestamp, do any of
your own pre-save processing
- post-save: I've finished saving, do any of your post-save processing
- other commands: the window might tell the "current TabPage" or
"current DataWindow" that a print operation, add row or delete row command was
initiated from the menu.
3. The Tab control needs to tell the Tabpages and
DataWindows about the following:
- first access: this is the first time you've been accessed (you may need
to retrieve your data or initialize yourself, if you didn't do so during window opening)
If this logic is placed into standard functions/events that the ancestor
automatically invokes/manages, then it is much easier to build and manage each descendant
Tab Detail Window.
Wizard Window
An ancestor wizard window could be created that included previous/next
buttons. The ancestor would manage the movement between these adjacent windows and it
might provide the capabilities suggested under the Detail
Window ancestor.
Error
Search/Maintenance Windows
You will need to develop your own windows to search for error message
and update them. PFC does not provide message search/maintenance windows for you.
Extensions to Specific PFC Services
Various extensions that your might consider to PFC Services are provided
under the following headings:
Logon Window (w_logon)
The standard PFC logon window lets the user exit the logon without
actually logging on. You might want to shutdown the application, if the user exits the
logon window without logging on. Also, PFC doesn't allow for retries on the logon window.
You might want to allow for the standard three retries and then shutdown the logon window,
if the user has three failed attempts.
DataWindow Extensions
(u_dw, etc.)
There is plenty of opportunity for writing reusable DataWindow
capabilities, on top of the rich PFC DataWindow function set. Suggested DataWindow
extensions are given below.
Standard u_dw Events:
- you could code the constructor event to automatically get the handle to
the ParentWindow and the Menu and then put these values into standard variables for
general use
- you could add code to pfc_preRmbMenu to call a placeholder function to
enable/disable any standard right mouse menu items you've created (e.g. a Lookup right
mouse menu item)
- you could code the ItemError event to do standard error handling (see code sample)
- there is no "post-clicked" hook in PFC to help you react to
changes in row selection but you could post a standard window-level function in RButtonUp,
LButtonUp and Clicked to provide such a hook.
- you could override the default DBError processing to collect and pass
along more DBError information (e.g. buffer, row, DW name) to the pfc_DbError event
Callable DataWindow Functions:
- you could write a few functions to determine whether the user supplied a
valid value in an enterable drop down (see tip)
- you could write a special DW-level message function that suppresses
messages during window closing as well as set focus to the DW before displaying any
message (see tip)
- you could write functions for disabling DW columns (i.e. protect column
and change background color). Versions could be created for a single column, multiple
columns or the whole DW. Similar functions could be created to enable columns or the whole
DW.
- you could write a general function to update those columns specified in
one array with values supplied in another array (useful for updating user id and timestamp
in DW's during pre-save processing).
- you could write functions which check for duplicate values in a DW (there
may be two individual unique columns and you want to ensure uniqueness before sending to
the database). The function might also handle the checking of compound unique values
(column 1 + column 2 together must be unique).
- you could write a function which sets focus to the tabpage that has this
DW (see tip)
- the base DataWindow services provide of_describe and of_modify functions
that make it somewhat easier to do DataWindow describes and modifies. However, you may
decide to develop specialized functions to help with some of the really common modifies
and describes (e.g. f_GetColumnColor, f_SetBackColomnColor, etc.)
- other miscellaneous functions include: a function to count the number of
selected rows, a function to count the number of rows in a DW child, a function to tell
you if a row is "new" (New! or NewModified!) or "existing"
(NotModified! or DataModified!), etc.
TreeView Extensions
(u_tv)
- coding can be made easier with simple supporting functions that set the
ItemPicture, StatePicture, OverlayPicture, bolding, etc. given a handle to a TreeView
item.
- you may have to write your own Find TreeView item function. The default
function overflows the stack (in the 16-bit environment in PFC5, see tip).
Alternatively, you might have searching requirements that aren't satisfied by the PFC
search capabilities.
Transaction Object
Extensions (n_tr)
- for non-DataWindow SQL statements or stored procedure calls, you might
want to write a general-purpose error checking function (see tip)
Error Service Extensions
(n_cst_error)
Aside from having to build your own windows to search/maintain messages
in the Messages table, you can make a few useful extensions to the message service:
- of_Message1: create a message function called of_Message1 that takes a
single message parameter. This saves the standard hassle of having to set up an array just
to pass a single parameter to the of_message function.
- Message Count: create a function called f_GetMessageCount() which checks
the number of messages. It is good to call this function after loading messages from a
text file there is no other effective way to confirm that the messages were
actually loaded successfully. With this function, it is easy to check that at least one
message got loaded or you might check whether an exact count was loaded.
String Service Extensions
(n_cst_string)
- ArrayToString: you might want to write an overloaded version for
"of_ArrayToString". This new function would also create a delimited string but
it would include entries for empty array entries. The PFC function doesn't leave a
"spot" for empty entries which can mess up positional handling. The default PFC
behaviour means that of_ParseToArray and of_ArrayToString aren't true opposites (i.e.
going back-and-forth doesn't give you back your original array, if there is an empty
entry).
- See array service and variable service for additional string-related services
New Services To Create
The new services below could be useful in augmenting PFC's functionality
for your application.
Array Service
PFC lacks an array service so you might want to develop one. Many of
array functions could apply to several data types (e.g. numerics, strings, dates). Some
useful array functions would include: Sort Array, Find Array Element, Add Array Element,
Remove Array Element, Remove Duplicate Array Entries, Sum up Array Elements and Check if
Arrays are Equal.
Variable Service
Aside from the above array service, there are a number of standard
functions that apply across variable types (and you might want to create
"overloaded" functions to handle the major variable types). The main purpose of
the functions listed below is to avoid the standard nuisance of constantly checking if
variables are Null or something else, which is a common PowerBuilder coding issue.
Specific null side-effects are discussed more fully on another page.
- f_IsEmpty: checks if a variable is "empty". For a string,
"empty" might be defined as Null, EmptyString or blanks. For a number, you might
give the option to treat both zero and Null as "empty". For a date (due to
PowerBuilder handling), you might treat Null and "01/01/1900" as
"empty" (see sample
code).
- f_NotNullValue: if the input value is Null, EmptyString is returned. This
makes it a string "safe" to concatenate with other strings (see sample code).
- f_IsEqual: takes two input parameters and checks if they are equal. It always
returns True/False, even if one or both input parameters are Null. If both parameters are
null, then True would be returned -- since they are equivalent for most programming
purposes (see sample code). In your function, you might also give the option to treat
"EmptyString" and Null as equivalent.
Note: When you get in the habit of using functions like those above, you
rarely run into a situation where "unexpected" Null conditions cause your logic
to fail.
Required Column Service
(not PFC's)
- if you want to overcome the drawbacks of PFC's Required Column service, you can
develop your own Required Column Service in about a day.
- the key to developing a Required Column service that has no side-effects
during DataWindow operation is not to use the DW Required Column checkbox (and then
try to fake it out in ItemError like PFC5 and PFC6 do). The no side-effect alternative is
to simply keep a "list" of required columns and then examine those columns
during the DW PreSave process. A sample design is specified below. This design and
variants on it have been successfully used on several large PowerBuilder projects with PB4
through PB6 with no side-effects.
Sample Required Column Service Design
- First decide what column-specific options your required column service
might require. Some ideas: the ability to treat EmptyString, blanks or zero as equivalent
to Null/missing for particular columns, the ability to specify a "special required
missing message" for a column, the ability to set focus to a particular column (i.e.
other than the missing column -- this might apply in certain code/description situations).
Required column options would be specified when "adding" each required column
(with standard defaults for simplicity).
- Write a function called f_AddReqColumn that lets the descendant specify
the required column names/options (the function would usually be called from the
descendant's DW constructor event). The function should callable multiple times so more
required columns can be added at any time. You might also provide a function
f_RemoveReqColumn function that lets the descendant indicate a column is no longer
required. Required column names and options can be stored in instance variable arrays.
- In the PFC u_dw's of_CheckRequired function (which is called as part of
PFC's PreSave processing), include a call to your own function (e.g. f_CheckReqColumns).
This function should check if any of the specified required columns are missing (e.g. Null
or possibly blanks, depending on the option). If a column is missing, parameters can be
returned to of_CheckRequired so that the appropriate message is displayed (if it is not
the default message) and so focus can be set to the appropriate column (if focus is not to
be set to the missing column).
- You should write f_CheckReqColumns to take "buffer" as a
parameter. This way it can be called twice: once for the Primary buffer and once for the
Filter buffer. If a filter buffer row with a missing required column is found, that row
would need to be de-filtered before a message was displayed to the user.
Miscellaneous
Support Functions
- Provide functions f_TodayNow, f_Today and f_Now which retrieve the
current date/time from the server (instead of the client). As a rule, the time should
always be obtained from the server to ensure consistency in a multi-user application.
Advanced Services
A number of more advanced services that would augment PFC are presented in this
section. These services would require more effort and more sophistication to develop. As
such, they would be more suitable for larger organizations. The extensions are presented
under the following headings:
Menu Services
- A menu service could add intelligence to the process of hiding and
showing menu items. It should support the hiding of toolbar icons when a menu item is
hidden. Also, intelligent hiding would cause menu separators to be hidden / shown
appropriately.
Window Services
- Window context: this service could allow a window to identify
itself with a logical key (e.g. a Customer Window might use the its "Customer
No." to identify itself).
- Window/Object messaging: a service could allow one object (e.g. a
window) to send a "message + parameters" through a standard message function to
a class of objects or to a particular object (e.g. to a window of a certain class with a
specific context).
- Parameter Manager: this service would provide a standard place to
pass parameters when opening windows or other objects (it would be more sophisticated than
using a StringParm and less cumbersome than using a series of purpose-built
PowerObjectParms)
- General Window Management: this service would keep track of each
window's "creator" window and its "logical parent" window (e.g.
Invoice would be the logical parent of Invoice Line). With logical parentage, the window
manager can automatically close logical child windows when the parent window is closed
(saving user effort). It can also send messages to all logical descendants and all direct
ancestors, when needed.
Help Services
- A Help service should have the ability to create, generate and integrate
help with the application. This would involve creating a standalone "workbench"
type of application.
- A Help service might also provide facilities to integrate help with third
party help tools.
Copyright © Woodger Computing Inc.