The BAPBUG Journal
Issue #1, March 1998


Welcome from our President
by Debbie Arczynski


How Not to Close a Window
by Greg Dzingeleski


Datawindow Ini File Service
by Greg Dzingeleski


Dynamically Changing Printers
by Susan Galli, CPD


Technical Advisor’s Corner


Event Driven Programming in PowerBuilder
By Fred Grau, CPD


Advanced Variable Declarations
by Rik Brooks


A Note from the Treasurer


Contact List


Welcome from our President

Welcome to the first issue of the Baltimore Area PBUG newsletter. This newsletter is YOUR newsletter! We hope that it is stimulating and helps you in your quest for the answer to the ultimate PowerBuilder question that you have on your mind! By the way,what is your question? We are going to include in each issue a Technical Advisor’s Corner in which your question can be answered, but we need to know what your question is. You can forward all questions to any of the technical advisors. Their phone numbers and email addresses are listed in the newsletter, along with all of the BAPBUG officers.

Please feel free to contact any of the officers if you have suggestions of topics for our bi-monthly meetings, concerns with the running/editing of the meetings or newsletter, or just comments in general. We also want to encourage you to submit small, medium, or large articles of interest to the PowerBuilder community. We would like to encourage you to submit any "PowerBuilder tips" that have helped you along your way and may help others, such as:

If a drop-down child datawindow has a retrieval argument, and the retrieve on the child retrieves no records, PB will display a dialog box, prompting for the retrieval argument. To avoid this problem, insert a blank record into the child datawindow before the retrieve.

We want to thank Greg Dzingeleski for volunteering to take on the enormous task of organizing and putting together this newsletter. Thanks Greg!

We hope to see you at our next meeting in April, and hope to hear from you soon!
Debbie Arczynski

February Meeting
Many thanks to Mark Pfeifer for presenting Silverstream, Bill Bitman for presenting the Window Plug-In, and Fred Grau for presenting Non-Visual Objects. If you would like any more information about any of these topics, please feel free to contact them. Their phone numbers and email addresses are listed in this newsletter.

Next Meeting
Our next meeting is Wednesday, April 8th at the OAG building at SSA. The topics for this meeting will be based on the PowerBuilder DataWindow. Mark Pfeifer will be presenting DataWindow Essentials. Susan Galli will be presenting Beyond DataWindows. If you have any questions about datawindows, this is the meeting where all those questions will be answered. Hope to see you there!

Future Meetings
Sybase (Powersoft) is scheduled to give a presentation called "From Here to the Web" on Wednesday, June 3rd. It will cover all the web enabled tools that exist in the Powersoft Tool Ensemble. They plan to discuss how to Web enable our existing PowerBuilder applications. This meeting should be exciting! Please plan to attend.

Interested in speaking at a future user group meeting?
If you are interested in speaking on a particular subject, or would like to demo your PowerBuilder application that you have been working on, please contact Debbie Arczynski at (410) 796-9461 (or debbie@charm.net). There are openings for presentations or demos of any length from 10 minutes to 2 hours. Thanks!

BAPBUG Web Page
Please take a look at our new web page designed by Anne Sola. It has lots of information about the group itself, the officers, the agenda, events, jobs, and links to other PB web pages. It is located at: www.geocities.com/siliconvalley/bay/8680. If you have any suggestions and/or comments, please contact Anne Sola.


How not to close a window

Recently I came upon an error in a program I'm maintaining that highlights interesting behavior of Powerbuilder script execution. In code similar to that shown in Listing 1 a null object reference system error occurred when the user clicked on the close button. This was noticed after the application was migrated from Powerbuilder 4 to Powerbuilder 5. The interesting behavior of the close function is that the function will close the window argument and then continue to execute the script that called the close function, even if the object that contained the script has gone out of scope. The script being executed is still retained although it's object is gone. In the code below a system error should alert the developer there is a problem, but if there was no reference to a null object (cb_ok) the script would execute and the developer may have no idea that the rest of the script has executed. But what happens in PB 4? Doing a debug revealed that the script continued to execute, but PB 4 missed the error and the system error was never triggered. The obvious solution is to use a return statement after the close(parent) to make the script stop execution.

Listing 1
long ll_ret
ll_ret=messagebox('Close','Do you want to save?',Question!, YesNo!)
if ll_ret=1 then
    close(this)
end if

//disable the ok button if the user didn't want to close
cb_ok.enabled=false

//Update important global variable
gl_imp = 64738

<By Greg Dzingeleski>


DATAWINDOW INI FILE SERVICE

I've taken many of the official Powersoft courses for Powerbuilder, and one thing they emphasize is using the datawindow (and now the datastore) as much as possible. They even claim it is useful for 'normal' screens where data is entered but not saved in a database, such as login screens. I've decided to take them up on this idea and I've tried it in a couple places to see if there really is an advantage. One of the places I used it is on a screen for database parameters to go on an ini file (I for one believe in the GUI idea and I think nobody, even a DBA or other administrator types should have to open up an ini file with a text editor ever). While I was at it, and being lazy when it comes to coding tedious things, I thought it would be neat to write a function that would read the datawindow for me and automatically write to the ini file. This would let me avoid the tedious dw_1.object.columnname… for every field. I could also write a function to populate the fields of the datawindow from the ini file. I would only have to make an external source datawindow and program the functions calls to read and save the data for the whole datawindow.

I wrote this code to fit in my framework, which is mostly service based. So in my ancestor datawindow I put an instance variable to refer to the service object u_dw_srv_inifile. I then made a function to initialize the service. I gave the function one boolean argument, using true to create the service and false to destroy it (see Listing 1). The function also calls a function in the service that tells it what object the service serves (the datawindow that called it into being). This is stored as a private instance variable of type u_dw.

Listing 1
u_dw_ancient
Instance variables:
Public u_dw_srv_inifile iu_iniservice

Function:
of_initiniservice(boolean ab_start) returns long

//starts the dw ini file service if true, destroys it if false
long ll_ret

if ab_start then //start up the service
        if not isvalid(iu_iniservice) then
                iu_iniservice=create u_dw_srv_inifile
                //tell the service whom it serves
                ll_ret=iu_iniservice.of_setrequestor(this)
        end if
else //destroy the service
        if isvalid(iu_iniservice) then
                destroy iu_iniservice
        end if
end if

return ll_ret 

The real parts of this code are the functions that read from and save to the ini file. Starting with the save function (because I have to figure out what I'm saving before I read it in), I want to call this function and have it do all the setprofilestring calls for me automatically. Setprofilestring has 4 arguments, the ini file name, the section in the ini file, the key in the ini file and the value to be inserted. I'm going to keep the ini file as an argument to my function, because I may have a need to use other ini files. The section in the ini file I'll leave as an argument also, because it's not much trouble to code it in. I'll use the field name seen in the datawindow painter as the key. This way when I create my datawindow all I have to do is name the fields with the names I want them to have in the ini file. The value I will get from the fields themselves.

The save function will then have two arguments, the ini file and the section name. First I want to perform an accept text to get all the data, this is a datawindow after all. Then I want to get the count of all the fields in the datawindow and loop through all of them. As I loop through them I want to read the column name to use as the key. I can read any of the datawindow data types into an any type variable. The string() function will turn them all into strings to use with the setprofilestring function. One complication exists however. If the data type is date or datetime the string function will return the date as a two digit year. I read somewhere that this was a bad thing, so I'll check the data type for each column. If it's a date or datetime data type I'll use a format string with the string() function call to get the format I want. Before I save to the ini file I want to change any null data for the value argument to a space so it will get saved, otherwise deleting the value in the datawindow won’t delete it in the ini file. If the setprofilestring fails, I'll exit the function giving a bad return code. Otherwise, I'll loop through the rest of the columns and reset the update flags before returning. The resulting code is in as below (Listing 2).

Listing 2

u_dw_srv_inifile.of_savetoini(string as_inifile, string as_section)

//This function scans through the datawindow and saves the data to the
//user specified ini file, using the user specified section and using
//the column name as the key. It then resets the update flags.

long ll_cnt, ll_rows, ll_ret
string ls_name, ls_data, ls_type
date ld_date
any la_data

idw_dw.accepttext()
ll_rows = integer(idw_dw.object.datawindow.column.count)

for ll_cnt=1 to ll_rows
        ls_name=idw_dw.describe('#'+string(ll_cnt)+'.name')
        //load in the data to an any variable, using a string will not properly convert
        //the date to a string
        la_data=idw_dw.object.data[1,ll_cnt]
        ls_type=idw_dw.describe('#'+string(ll_cnt)+'.coltype')
        choose case ls_type
                case 'date'
                        ls_data=string(la_data,'mm/dd/yyyy')
                case 'datetime'
                        ls_data=string(la_data,'mm/dd/yyyy hh:mm:ss:fff')
                case else
                        ls_data=string(la_data)
        end choose
        //change blanks to a space so it is saved
        if isnull(ls_data) then
                ls_data=' '
        end if
        ll_ret=setprofilestring(as_inifile,as_section,ls_name,ls_data)
        //return of -1 means setprofilestring couldn't access the as_inifile ini file
        if ll_ret<=-1 then
                return -1
        end if
next

idw_dw.resetupdate()

return 1 

The second part of the code is loading in what has been saved. The function will need to know what ini file to use and what section in the ini file to read. This is the opposite of saving except for the data conversion. As all the data coming in is text it must be converted to the proper type before insertion into the datawindow, otherwise no data will be entered using the setitem() function. As before I count the number of columns, get the column name, use that as the key in the ini file and retrieve the data. Then I have to check the data type of the column and do the appropriate data type conversions. Using the describe function to describe #colnumber.Coltype I can get back many different answers in the case of char (char(20), char(200), etc) and decimal (decimal(2), etc) because it also includes the length as well as the data type. So instead of doing a choose case on the Coltype I take only the first 5 characters. I couldn’t get the datetime function to return a datetime from an ini file value (is it me?), so I had to parse the string into a date string and a time string. I check for an error during the setitem() and return with an error code if necessary. At the end I reset the update flags of the datawindow. The resulting code is:

of_fillfromini(string as_inifile, string as_section)

//This function reads the column names from the requestor datawindow and
//retrieves values for these by reading a user specified inifile and
//section, using the column name as the key. It then resets the update
//flags on the datawindow.
//

long ll_cnt, ll_rows, ll_data
string ls_name, ls_data, ls_type
date ld_data
datetime ldt_data
time lt_data
decimal ldec_data
long ll_len, ll_size, ll_ret
string ls_piece

//count the number of columns in the dw
ll_rows = integer(idw_dw.object.datawindow.column.count)

for ll_cnt=1 to ll_rows
      //get the column name
        ls_name=idw_dw.describe('#'+string(ll_cnt)+'.name')
        //read the data using the column name as the key
        ls_data=profilestring(as_inifile,as_section,ls_name,'')
        //check the datatype
        ls_type=idw_dw.describe('#'+string(ll_cnt)+'.Coltype')
        //convert to the proper datatype
        //use left(,5)--to cut off the end piece, strings are type char(20), char(200), etc
        //decimals are decimal(2), etc.
        choose case left(ls_type,5)
                case 'date'
                        ld_data=date(ls_data)
                        ll_ret=idw_dw.setitem(1,ls_name,ld_data)
                        setnull(ld_data)
                case 'numbe','long','ulong'
                        ll_data=double(ls_data)
                        ll_ret=idw_dw.setitem(1,ls_name,ll_data)
                        setnull(ll_data)
                case 'real','decim' //decimal(#)
                        ldec_data=dec(ls_data)
                        ll_ret=idw_dw.setitem(1,ls_name,ldec_data)
                        setnull(ldec_data)
                case 'datet' //datetime
                        //extract the date
                        ll_len=len(ls_data)
                        do until ll_size>ll_len or isdate(ls_piece)
                                //go backwards to ensure all 4 digits of the year are taken
                                ll_size=ll_size+1
                                ls_piece=left(ls_data,ll_len - ll_size)
                        loop
                        ldt_data=datetime(date(ls_piece),time(right(ls_data,ll_size+1)))
                        ll_ret=idw_dw.setitem(1,ls_name,ldt_data)
                        setnull(ldt_data)
                 case 'time'
                        lt_data=time(ls_data)
                        ll_ret=idw_dw.setitem(1,ls_name,lt_data)
                 case else //for 'char(#)'
                        ll_ret=idw_dw.setitem(1,ls_name,ls_data)
      end choose
      if ll_ret=-1 then
            return -1
      end if
next

idw_dw.resetupdate()
return 1

Listing 3

Using the functions in this service is simple. Simply create a datawindow with an external data source. Name each column with the same name you want the ini file key to have. In either a post-open event for the window or constructor of the datawindow call the function to initialize the service. Load in the ini file data by calling the function of_fillfromini() passing the ini file name and section as arguments. That’s all (and of course check the return code). Saving the data uses just one function of_savetoini() with the same arguments. The only thing you lose with this system is being able to determine the default values when loading in the data if reading the ini file fails for some reason. Reading an ini file is so fast that you can do some post processing if necessary with little appreciable effect. I’ve used this service and then encrypted/decrypted a database password directly after the save/fill function call. What you get from this service is a simplification of a programming chore. It is a small matter to add and delete fields and to change the display format from one control type to another (radio buttons to drop down list box for example). You can switch the display very easily. As an example imagine you handled connection information for different databases each with a different dbms. You can change the display to show the valid connection parameters for that particular dbms using one screen by simply switching the datawindow dataobjects. You also get all the datawindow functions you know and love like modifiedcount() and resetupdate().

The code needed to create the ini file service is pretty basic. The original version only used a character data type, the rest were added for this article. Thus, this hasn’t been extensively tested, so you better test it yourself. I did find that the real data type would sometimes not write correctly to the ini file. Just now it changed 2.51 into 2.509999990463257. If this is used just to write ini files it shouldn’t be a problem, just use the number or decimal data type. I don’t imagine in a simple system it would be necessary to use data types other than date, string and decimal. I just included the other data types to be complete.

By Greg Dzingeleski, PhD. Greg works at the Social Security Administration and has been programming in Powerbuilder since October of 1996. Greg can be reached at 410-966-3328 or at Greg.dzingeleski@ssa.gov, but you should be warned he’ll probably try to get you to write an article for the newsletter.


Dynamically Changing Printers

At the last meeting of the Baltimore Area PowerBuilder Users Group meeting, I asked if anyone knew how to dynamically change the system’s default printer without the user having to select the printer. Here’s what I found out in researching the issue.

Background

One of my clients has an application that generates letters to thousands of people. The current system used a very slow method of embedded SQL, then DDE to WordPerfect to produce letters, labels and other documents. The application automatically changes the printer by sending a DDE command to WordPerfect to select the appropriate printer depending on the document being printed. Although the application was written in PB, it was a good example of how to do everything wrong! The client had let the letter generator run for three days before rebooting and deciding it didn’t work. When called in, I determined that in fact this piece of code was working but the performance was unacceptable, to say the least!

As an aside, you might be interested in how bad can code get? Well, since you ask…This part of the application is a batch process which checks the records in the database, selects certain records, generates a document based on the indicator in the database and prints it out. The current code has embedded SQL as I mentioned, but not just one, not just two, but upwards of eight separate statements! Then it opens WordPerfect, sends over twenty-eight data fields via DDE, merges, the document, changes the printer, prints and then closes WordPerfect. It does this for each record.

We got rid of the SQL by using datawindows and got rid of the DDE by replacing it with the RTF datawindows. It now takes a few seconds to run. But how to change the printer?

 Solution

After searching the WATCOM C++ API Help library, I posted a message to the PowerBuilder news group for datawindows (powersoft.public.powerbuilder.datawindow) to find an easier solution. This resulted in a tip from Mark Pfeifer to check out the http://www.dejanews.com/ to search for topics on default printers. This utility searches many newsgroups for postings on a given topic. There were many, many messages. My favorite answer was the very crisp "It’s considered rude to change the default printer. Let the user change it themselves." I do hope the author of this tidbit is not responsible for developing applications. I did find many legitimate leads, which basically fall into three categories.

API Calls: This solution deals with calling the API functions to set the default printer. A simpler approach is to purchase something like PowerPrint from DigitalWave (http://www.digitw.com/) which is a product that allows the developer to change all the printer attributes.

Registry Entries: This approach centers on using the Registry functions in PB to get and set the Registry entries for printers. Ignoring the fact that function calls are very long, e.g. to get the default printer is

RegistryGet("HKEY_LOCAL_MACHINE\Config\0001\System\CurrentControlSet\Control\Print\Printers", "default", ls_default),

the function calls change depending on which Windows system your using.

INI Files: Being the last option, you can tell this is the one I liked the best. In short, you get the current default printer by getting the device in the windows section of the win.ini file. Set the default by setting the value of the device in the same section to the printer you want, for example, SetProfileString(‘win.ini’, ‘windows’, ‘device’, ls_default). Below is the full solution.

The Details

Defining the Printers in the INI files

We defined the printers we need in the application by listing them in the application ini file. These need to be configured on the machine being used. You can find the current configurations by going to the Devices section of your win.ini file.
e.g. [devices]
      Epson ActionLaser 1500=HPPCL5MS,LPT1:

In the application ini file, this becomes
      [printers]
      LASER = Epson ActionLaser 1500,HPPCL5MS,LPT1:

Note that the equal sign becomes a comma in your ini file. The format for the default device in win.ini is Device={Device name}, {Device language}, {network path/port}.

We listed printers for Dot, for preprinted forms on the dot matrix, Laser, Label, Fax, and Color.

 A Function to Define Printers in the Application

In the non-visual object that handles the Ini file services, we defined six strings as instance variables to correspond to the five printer types plus one string for the user’s current default printer. The non-visual is declared as a global, gnv_ini.

      PROTECTED:
      STRING is_laser, is_color, is_dot, is_fax, is_label, is_default

Create a new function to read the printer settings from the application’s ini file. If the printer is not defined in the ini file or configured in the win.ini file the value will be an empty string.

PROTECTED Function of_get_printer (String a_printer_type, String a_key)

      STRING ls_printer, ls_look_up

      //Get the printer name from the app’s ini
      ls_printer = this.ProfileString(is_ini, ‘PRINTERS’, a_key, ‘’)

      //Let’s make sure that the printer is configured on this machine
      IF ls_printer > ‘’ THEN
            //Get the printer name from the app.ini entry
            ls_look_up = LEFT(ls_printer, POS(ls_printer, ‘,’, 1) – 1)

            //Make sure it’s listed in the devices of the win.ini file
    ls_look_up = ProfileString(‘win.ini’, ‘devices’, ls_look_up,’’)
            //if it’s not there, set the printer to empty string
            IF NOT (ls_look_up > ‘’) THEN
                  ls_printer = ‘’
            END IF
      END IF
      RETURN ls_printer

In the code where the gnv_ini object initializes itself, we added the following code to set the values of the instance variables to the string that we need to set the device in the win.ini file to the corresponding printer.

      //Get the user’s current default printer and save it
      is_default = ProfileString(‘win.ini’, ‘windows’, ‘device’, ‘’)

      //Get the values of each printer using the key name of the printer
      // in the application’s ini file
  this.of_get_printer(is_laser, ‘LASER’)
  this.of_get_printer(is_dot, ‘DOT’)
  this.of_get_printer(is_label, ‘LABEL’)
  etc. 

The Function to Change Printers

Now we’re ready to actually change the default printer on the fly.

      Public Function of_set_printer( string a_new_printer) RETURN Boolean

      INTEGER li_set = 0
      STRING ls_printer
      BOOLEAN lb_set

      //Find out which printer is selected
      CHOOSE CASE Upper(a_new_printer)
                  CASE ‘LASER’
                        ls_printer = is_laser
                  CASE ‘LABEL’
                        ls_printer = is_label
                  CASE ‘DEFAULT’
                        is_printer = is_default
                  etc.
                  CASE ELSE
                        ls_printer = ‘’
      END CHOOSE

      //If we have a legitimate printer type, let’s set it
      IF ls_printer > ‘’ THEN
      li_set = SetProfileString&
            (‘win.ini’, ‘windows’, device, a_new_printer)
      END IF

      //If the SetProfileString didn’t work or the printer was not valid,
      // ask the user which printer to use
      IF li_set <> 1 THEN
            MessageBox("Set Printer", "Unable to set the " + a_new_printer &
                        + " printer. Please select a printer.")
            li_set = PrintSetUp()
      END IF

      //set the return to true if we succeeded
      IF li_set = 1 THEN lb_set = TRUE

      RETURN lb_set

 Changing the Printer Dynamically

We have everything in place now. To change the printer, just use the following function call

      gnv_ini.of_set_printer(ls_printer)

where ls_printer = ‘COLOR’ or ‘LASER’ or ‘DOT’ or ‘DEFAULT’ or any printer that has been set up.

 Remember to also set the printer back to its original default by putting the following code in the Destructor event of gnu_ini.

      This.of_set_printer( ‘Default’)

Conclusion

Unlike the guy who wrote that it is "rude to change the printer", I thing that if the user tells the application once that for labels we want to use the label printer, then the application should do it until told otherwise.

Copyright 1998 by Chi Resources. All rights reserved.


Submitted by Susan Galli. Susan is an independent consultant, a CPD, and the original technical advisor for BAPBUG as well as serving on the executive committee since its inception. She has worked with PowerBuilder since 1992 - programming, teaching and mentoring. She can be reached at (410) 987-2684 or susan@annapolis.net.


Technical Advisor’s Corner

As I understand OLE I can access any OLE object through Powerbuilder, but I need to know what functions to call. I've also read that you can access Powerbuilder itself as an OLE application server. Does anyone know where I can get the function calls for the powerbuilder application server?

Speaking of OLE here's something I've fooled around which is kind of interesting. In windows 95 or NT you can create a control panel folder. In explorer create a new folder and name it

Control Panel.{21EC2020-3AEA-1069-A2DD-08002B30309D}

I'm not kidding. When you type in the new name and hit return the folder changes name to Control Panel and now "points" to your control panel. You can put this in your start menu and it will open as a folder and the items will be control panel items. It will be updated if you add or delete control panel items. I don't know why this wasn't included in the normal operating system. Anyway, as far as I can tell the long number/letter sequence is the GUID or globally unique identifier that each OLE object has. If you look in the registry under the key HKEY_CLASSES_ROOT\PowerBuilder.Application\CLSID there is another GUID ({29478EF0-C371-11CE-8891-00805F70D324} if you're curious). I can give a folder any name followed by a period and the GUID (brackets included) the folder changes from a folder type to a Powerbuilder 5.0 Automation type. When I double click on it it does...nothing.

I just thought this was neat and is probably useful is some cases, but I don't know how. If anybody would like to expand upon this it would be appreciated. You could even write it up as an article. Two other useful GUIDs are Printers.{2227A280-3AEA-1069-A2DE-08002B30309D} for the printers folder and Dial Up Net.{992CFFA0-F557-101A-88EC-00DD010CCC48} for dial up networking.

Greg Dzingeleski


Event Driven Programming in PowerBuilder

By Fred Grau, CPD

One of the most difficult aspects of developing a Windows based client/server system is understanding event driven programming. Add object-oriented techniques to the mixture and you’re talking total confusion. This article will attempt to alleviate some of this fear.

There are many pre-defined events assigned to window. Some of these events trigger when the user performs a specific action. These events include Clicked, Doubleclicked, and Rbuttondown. However, many of these events automatically trigger depending on the circumstance. For example, if window A and window B have been opened, and the user changes focus from window A to window B without closing window A, the following events will automatically trigger:

  1. Window A’s Deactivate event.
  2. Window B’s Activate event.

If your application has a frame menu but does not have a menu assigned to each sheet window, then these two events are appropriate for enabling and disabling menu items. However, as a general rule, I try to avoid having any code in these events.

As another example, these are the events that automatically trigger when a sheet window opens:

  1. The Constructor event for each control on the window. Do not try to reference any controls on this window because you’ll probably get a null object reference. If a parm is passed to this window, then don’t perform any action based on this parm in this event.
  2. The window’s Open event. Most of your code is placed here.
  3. The window’s Resize event. If your window was opened as Layered! or Cascaded!, this event will resize the window.
  4. The window’s Show event. This event will display the window to the user.
  5. The window’s Activate event. This event also gets triggered when the user changes focus from another window to this window.
  6. Any events or functions "posted" to from script in the open event. To give a user the perception that a window is opening faster than it actually is, I like to code: This.Post Event ue_retrieve() in the Open event. The posted ue_retrieve() event will retrieve the data for the window which will occur after the window is displayed to the user.

The events that automatically trigger in a DataWindow control are more confusing than a window. For example, in PowerBuilder 5.0, the following DataWindow events automatically trigger when a Retrieve() is performed:

  1. RetrieveStart – This event occurs before the retrieval begins. If 1 is returned, the retrieval is cancelled. If 2 is returned, then the retrieval will append the retrieved rows to the existing rows in the DataWindow. If 0 is returned (default), the DataWindow is reset before the retrieval begins.
  2. RowFocusChanged – If rows previously existed in the DataWindow the row number changes to 0.
  3. SQLPreview – The sqlsyntax parm to this event contains the actual SQL submitted to the Database Management System (DBMS). This event can be used to display or change the SQL.
  4. RetrieveRow – This event occurs after each row has been retrieved. A script in the this event, event a comment, can significantly slow down the time it takes to complete a query. If 1 is returned, the query is cancelled.
  5. Resize – This event occurs after a page of data is retrieved and there’s a vertical scrollbar.
  6. RowFocusChanged – This event is triggered even if no rows are retrieved.
  7. ItemFocusChanged - This event is triggered even if no rows are retrieved.
  8. RetrieveEnd.

Under PowerBuilder 6.0, the following DataWindow events automatically trigger when a Retrieve() is performed:

  1. RetrieveStart.
  2. RowFocusChanging – This event gets triggered if there are already rows in the datawindow. The newrow parm in this event is 0. If 0 is returned, the RowFocusChanged event is triggered. If 1 is returned, then the current row number remains unchanged.
  3. SQLPreview.
  4. RetrieveRow.
  5. Resize.
  6. RowFocusChanging – This event only triggers if the current row number is not 1. If 0 is returned, then the RowFocusChanged and ItemFocusChanged events are triggered.
  7. RetrieveEnd.

When a DataWindow Update() executes, the following DataWindow events trigger:

  1. UpdateStart – This event triggers before the SQL is sent to the DBMS. If 1 is returned, then the update is cancelled.
  2. SQLPreview for each changed row – Deletes occur first, updates second, and inserts last. The rows in the Filter buffer are included with the update.
  3. UpdateEnd.

After data is retrieved for a DataWindow, events can automatically trigger under certain circumstances. For example, if a row is current, and the user clicks on another row, the following DataWindow events trigger:

  1. ItemChanged – This event will only trigger if data was previously entered in another column in the DataWindow.
  2. Clicked.
  3. RowFocusChanging (PowerBuilder 6.0 only) – If 0 is returned, then the RowFocusChanged and ItemFocusChanged events are triggered. Returning 1 will prevent these events from triggering.
  4. RowFocusChanged – Try to place any row selection logic in this event.
  5. ItemFocusChanged.

If the focus was on another control before the DataWindow was clicked, then these events will trigger prior to the events listed above:

  1. LoseFocus – From the previous control that had focus.
  2. GetFocus – For this DataWindow control.

If a user clicks on a new column in a DataWindow control without clicking on a new row, then the follow DataWindow events trigger:

  1. ItemChanged - This event will only trigger if data was previously entered in another column in the DataWindow.
  2. Clicked.
  3. ItemFocusChanged.

When a user enters data in a DataWindow and tabs to or clicks on another column in the same DataWindow, then the ItemChanged event triggers. This event occurs before the data is applied to the DataWindow. If 1 or 2 is returned from this event, then the data will not be applied to the DataWindow.

As a caveat to DataWindow events, when a user previously entered data in a DataWindow, then clicks on another control, the DataWindow’s ItemChanged event does not automatically trigger. An AcceptText() must be executed for the entered data to be applied to the DataWindow. Because of this, always issue an AccepText() before updating any DataWindows.

With all of these events automatically triggering, life for a developer can be very confusing. I hope some of this article clears up some of this confusion.

Fred Grau is a PowerBuilder consultant and the only employee at Woodfield Technical Services, Inc. He is also a CPD and the treasurer for BAPBUG. He has developed a variety of PowerBuilder applications at locations including VIPS, HCIA, Blue Cross and Blue Shield of Delaware, and SSA. You can reach Fred at (410) 893-9638 or fred1@flash.net.


Here's an article from Rik Brooks the author of one of my favorite web sites, Rik's Powerbuilder Dojo (http://www.1313mockingbirdlane.com/dojo).

Advanced Variable Declarations

One of the most important aspects of OOPS is the relationship of one object to another. It's a kind of odd thing, this relationship. There are actually two levels to it. There is the relationship of one object to another at run time, while your application is actually in operation. Then there is the relationship of one object to another along the inheritance tree. That is to say; two or more objects can have a relationship while the application is running and simoultaneously have a relationship with its ancestors.

This article concerns both and might be a little confusing for that reason. I'll try to keep this discussion as concrete as possible and avoid some of the typical difficulties. Each object has something called an interface. That is the hooks that it provides to other objects to get at the internal workings of the object. Often the interface is a set of functions. Most objects, visual ones at least, have a property called visible. It is a boolean and if you make it false it becomes invisible. This property makes up one part of its interface.

The objects that you create also have interfaces. For some people these interfaces are wide open. The programmer, usually out of either laziness or inexperience makes all of the instance variables of an object public (the default). This means that each one of these variables are part of the interface. Any other object can reach inside the object and change any of those variables. Usually this isn't a good idea. It might be OK, but it is not good programming practice and is certainly a violation of data encapsulation.

This article refers to the declaration of instance variables. In PowerBuilder 4 instance variables could be defined as either Public, Protected, or Private. A publicly declared instance variable (the default) is accessable to any other object without discrimination. A private was accessable only to the class in which it was declared. A protected was accessable to the class in which it was declared and any descendent of that class.

This is all very good and understood. You can limit the ability of other objects to change the values of your object. PowerBuilder gives us a lot more flexibility than these rudimentary controls.

In PowerBuilder you can control something called the Access of each of these variables. You can declare that a particular variable has a different read or write access. Note that in the syntax of PowerBuilder 4 both read and write access had to be the same. The access rights still default to Public.

After PowerBuilder 4 you can declare an instance variable to have a read access of public and a write access of protected. You might want to do this if you had an object of type Human. This object would need to allow other objects to know how old it was. Other objects would need this information to meet recruitment requirements, or to serve it a drink. In this case you would declare the ii_age instance variable of the Human class thusly:

public protectedWrite int ii_age

This indicates that the reading of ii_age is public, but the writing is restricted to the Human class itself or any descendent of it. This is exactly what we need, without any functions. Of course since public is the default it would not be needed. Thus the following is synonimous:

protectedWrite int ii_age

There is one last point to be made and then I am done with this. Notice that we have a general access description, in our case public. Then we have one that further restricts the access. Finally we have the data type and the name. There are some restrictions to how you can declare the variables. The second access right can not be less restrictive than the first. That is to say, if you declare a variable to be private, you may not give it protecedRead. In other words, the following is illegal.

private protectedRead int ii_age

That is why there are only privateRead, protectedRead, privateWrite, and protectedWrite modifiers.

Let's finish this with a few examples

1.int ii_age
2.protectedRead ii_age
3.protectedRead protectedWrite ii_age
4.privateWrite ii_age
5.protectedRead privateWrite ii_age
6.protected privateWrite ii_age

1 is entire public (the default)

2 can be read only be the class and its descendants, but can be changed by public (kind of wierd)

3 is the same as protected ii_age.

4 can be read by public, but only the class itself can change it (very reasonable).

5 can be read by itself and descendants, but only changed by itself.

6 is the same as 5, just declared in a different way. Use whichever strikes your fancy.


Rik Brooks is an independent consultant working in the NJ,Penn, and Del area. He also takes off-site contracts. He is the author of "Rik's PowerBuilder Dojo" which can be found at http://www.1313mockingbirdlane.com/dojo 

A Note from the Treasurer

With the release of our first newsletter, we are offering the opportunity for corporate sponsorship of BAPBUG. Because our bimonthly newsletter will reach approximately 200 developers, we are offering the following annual rates:

Page Size Rate

¼ Page

$35

½ Page

$50

Full Page

$100

 

We will also be including an employment opportunity section in our newsletter. The cost per ad in this section will be $10 per issue.

Since we will be transmitting the newsletter electronically, we will need a softcopy (preferably in MS Word format) of any graphics to be included with your ad.

Please make checks payable to BAPBUG and can be sent to the following address:

BAPBUG
c/o Fred Grau
2003 Copperwood Way
Fallston, MD 21047

If there are any questions, feel free to contact Fred at 410-893-9638 or at fred1@flash.net.

Contacts

BAPBUG Officers
President Debbie Arczynski debbie@charm.net (410) 796-9461
Vice President Bill Bitman bitman@jhuapl.edu (410) 792-6000
Secretary Gordon Giffen gordon-g@vips.com (410) 832-8300
Treasurer Fred Grau fred1@flash.net (410) 893-9638
Tech Advisor Robin Bates robin.bates@powercerv.com (703) 502-8383
Tech Advisor Susan Galli susan@annopolis.net (410) 987-2684
Tech Advisor Mark Pfeifer mpfeifer@sprynet.com (410) 499-8765
Web Co-ordinator Anne Sola anne-s@vips.com (410) 832-8300
Newsletter Editor Greg Dzingeleski greg.dzingeleski@ssa.gov (410) 966-3328

BAPBUG Web Page

Please take a look at our new web page designed by Anne Sola. It has lots of information about the group itself, the officers, the agenda, events, jobs, and links to other PB web pages. It is located at: http://geocities.datacellar.net/siliconvalley/bay/8680. If you have any suggestions and/or comments, please contact Anne Sola.

Back to Home Page

1