Service objects were introduced for PowerBuilder at about the same time as the PowerBuilder Foundation Classes. Much like the PFC, most programmers found reasons not to use them. In this article we will reduce the complexity of these objects and in so doing perhaps open a new world of efficient objects that are easily maintained and understood.
This article is going to create a service object for the DataWindow. The idea behind this object and others like it is to minimize the footprint of the DataWindow. We want to minimize the amount of memory that the DataWindow requires and thus provide the greatest responsiveness that we can.
One of the most used areas of functionality for a PowerBuilder programmer is row selection for the DataWindow. How many times have you gone to the Clicked event of a DataWindow control and typed something like the following?
This.selectRow(0, FALSE)
This.selectRow(row, TRUE)
Most of us have probably written something similar to this hundreds of times. It gets even worse when we have to do something a little more complex such as an extended selection that handles ctrl-clicks as well as shift-clicks. It's when we write the shift-click functionality with its instance variable anchor row and dozens of lines of code in the Clicked event that we start thinking about reusability...and reusability is a key word in object orientation.
Most of us wind up inheriting our own DataWindow control from the standard DataWindow. We then put that code in the ancestor DataWindow. Before long the clicked event of your u_dw becomes huge. That might not be a bad thing except that this code for selecting rows is only used if your presentation style is tabular or grid. In all other cases you are carrying around a lot of code that you won't use. Again, if it were only the row selection of your DataWindow then maybe it wouldn't be so bad, but when you consider all the places in your code where you have this sort of thing happening, you wind up with applications that run more slowly than they need run and use more memory than they need.
Please take a look at Figure 1. You see the Ancestor DataWindow. There are three service objects available to it (in this example). Let's assume that this is a tabular presentation style. There would be no need for the graph helper or the treeview helper. That's why there is a connection between the ancestor DataWindow and the row selector object.
The connection is handled with a call to of_register.
I use the same metaphor to connect service objects to the objects that they service no matter what the details. The service object has an of_register function that is called by the object being serviced. The of_register has an argument that allows service object to "remember" the instance of the object that it is servicing by setting an instance variable. (Whew, isn't that as clear as mud?)
Let's take our example. Let's create a row selector object. So just fire up PowerBuilder and create a custom class. Give it three instance variables:
private u_dw iuo_dw
private string is_selection_mode
private long ll_anchor = 0
Now that you've entered that you can save your object. I call mine n_cst_dw_row_helper. Of course the n_cst means Non-visual Custom but I like to add the datatype that a service object is servicing so it is sorted in the pbl. In this case it's a DataWindow or dw.
Now let's look at the of_register function in that object.
iuo_dw = adw
Here you see that I only have one line of code. I pass in an argument of type u_dw (yes, you can DO that) and then set the instance variable to that object. Now the service object "knows" about the serviced object and can reference it by referencing iuo_dw.
To make this picture complete, let's look at where the of_register function is called. That would be inside u_dw.
First we create u_dw. That's done by creating a new standard type class. Then we select DataWindow from the dialog that appears. Let's look at the instance variables first:
private string is_selection_mode = "n"
private n_cst_dw_info idw_info
private n_cst_dw_treeview_helper idw_treeview
private n_cst_dw_graph_helper idw_graph
private n_cst_dw_row_helper idw_row_helper
In this case we are most interested in the last line of the instance variables, the one that holds a pointer to the row helper. Of course this isn't instantiated so it's not the whole story. We instantiate the row helper in the function where we set the selection mode. Why? Because the row helper isn't needed unless we set the selection mode - that is to say, a selection mode is required for the row helper to work. Let's look at the function of_selection_mode in u_dw.
if not isValid(idw_row_helper) then
idw_row_helper = create n_cst_dw_row_helper
idw_row_helper.of_register( this)
end if
return idw_row_helper.of_selection_mode( as_mode)
Now we are starting to tie the pieces together. We create an of_register in the helper. Then we called that function in the of_selection_mode function. Now the service object knows about the object to be serviced (n_cst_dw_row_helper knows about u_dw) and the object to be serviced knows about the service object; we have established that link.
In the last line of the function we called of_selection_mode in the service object though. Let's take a look that function and see what it does (see Listing 1). (Listings 1-4 can be downloaded here.)
All the of_selection_mode function does is makes sure that the argument is valid, sets an instance variable, and returns the old value.
We are almost there now. Let's look at the clicked event for our DataWindow shown in Listing 2. It is in this event that we would use the row helper.
The first line is very important. We have to always test to see if idw_row_helper, or any service object for that matter, is instantiated.
Once we know that it is we simply call the of_clicked function in the service object. Since the extended selection mode needs to respond to the ctrl and the shift keys, we will need to pass those. That's the reason that I did the keyDown function call.
Since we called the of_clicked function in the row helper, we should look at it (see Listing 3).
This function simply looks at the current selection mode (remember, we set that to instantiate our service object. Based upon that mode it either just returns or it calls functions that handle the row selection in a specific way.
Finally let's look at the of_toggle_row, of_extended_row and the of_listbox_row and we'll have all the pieces explained (see Listing 4).
We have achieved our goal. We have a row helper that can be implemented with just a few lines of code. The actual functioning of the object is nicely segmented in individual functions so they are easy to find. To round this out let's take a look at how easy it is to use our new object. The goal is to have an application that looks like Figure 2.
Of course the first thing we need to do is code an open event for our application object. I created an empty window so I could put a call to open it in the application open event:
SQLCA.of_auto_display_message( TRUE)
SQLCA.DBMS = "ODBC"
SQLCA.AutoCommit = TRUE
SQLCA.DBParm = "ConnectString='DSN=EAS Demo DB V115;UID=dba;PWD=sql'"
connect using sqlca ;
open(w_main)
I always use the example database with my example applications since all of you have that database. The DataWindow is tabular. The datasource is sql select and the sql for it is below:
SELECT "employee"."emp_id",
"employee"."emp_fname",
"employee"."emp_lname"
FROM "employee"
Now we put a u_dw onto the surface of the w_main. I'm assuming that you know how to do that. I named my control dw_emps.
Now we need the obvious two lines of code that are always needed to retrieve a DataWindow. I put mine in the post_open event of my window (okay, I inherited the window but that's beyond the scope of this article).
dw_emps.setTransObject(sqlca)
dw_emps.retrieve()
Finally we need to put in the radio buttons at the right and put a group box around them. Here is the code for each of the radio buttons. It should be obvious by now what they do:
dw_emps.of_selection_mode( "normal")
dw_emps.of_selection_mode( "extended")
dw_emps.of_selection_mode( "extended")
dw_emps.of_selection_mode( "toggle")
So once you have your service object created, all you have to do to make this work is the single call to of_selection_mode. That call instantiates the service object if needed and just uses it if not.
Finish your application off with a command button to close your application and you now have exercised your app.