Tips for PowerBuilder 5, 6, 7, 8

 

Some of these tips answer very frequently asked questions. Others are just plain interesting.  Most are from 

public postings on PB newsgroups & lists -- some by our staff, many by others. In some cases we lost 

track of the name of the author. If we have included in this list something that you wrote, and if you would like 

attribution, please drop us a line. If you would like to make some other kind of correction or your own contribution,

we'd be delighted to hear from you.


Topics 


Alter Table

Alter Table - Adding not-null columns

Array - Changing from bounded to unbounded 

Autologoff Bitwise and - Alternative 

Colours 

Command line parsing 

Connecting to multiple databases 

DDLB and dddw events 

DDDW calculator and calendar 

Deployment - dlls 

Directory exists 

Directory file list without a window 

Disable close 

Dot notation - limitations in 5.0.0x 

DW - changing the presentation style 

DW - currentrow() and getrow() 

DW column - highlighting of current row 

DW column - setting value of computed column 

DW - referencing computed fields 

DW - finding column names at runtime 

DW - finding controls at runtime 

DW - preventing focus loss on tabbing from last row or column 

DW GetRow() bug fix 

DW grids, changing 

DW - group by N 

DW horizontal lines every rows 

DW retrieval argument using aggregate function 

DW update error, row changed between retrieve and update 

Enter key - make it emulate the tab key 

Environment variables 

FindFirstFile api 

Free disk space 

Hex strings 

Login - Getting netware login name 

Login - Netware 

Mail - Sending from PB app 

Message Object - extending 

Mouse Position 

Multi-language support 

Network Drives 

PFC DDDW - multiple rows from a lookup 

PFC Help toolbar button 

PFC Numeric service - hex extensions 

PFC Transactions - optimistic locking, one opinion 

PFC Windows - placing controls 

Set default printer from within PB 

Sql/Oracle PB bug 

Stored Procedure with Output Parameter

RMB Menus 

TCP/IP address 

Window Events 

Window - Centre on opening

 

 

ALTER TABLE

 

1. Export syntax of the table to the log. 

2. Save the log as a SQL file

3. Drop the table.

4. Go into the DBA Admin painter, open the SQL file that contains the syntax of the table.

5. Make changes.

6. Execute the SQL. This will create the table again with the field options changed.

 

 

 

ALTER TABLE - ADDING NOT-NULL ATTRIBUTES OR COLUMNS

 

A table doesn’t have to be dropped or empty to add a NOT NULL constraint.  You have only to …

  - make sure all rows are indeed not null on the target field

  - write and run the alter statement in the DBA painter

You have to leave and re-enter the painter to see the changes.

 

If new not null columns are to be added you either have to start with an empty table or do it in 3 steps: 

first add the column, then fill in some values and finally set it to not null.

 

Back to top

 

 

 

ARRAY – CHANGING FROM BOUNDED TO UNBOUNDED

 

string lsNull [ ], lsBounded [ 5 ], lsUnbounded [ ]

// assume lsUnbounded has been populated elsewhere.

lsUnbounded = ls_Null

lsUnbounded = ls_Bounded

 

Now unbounded array will contain the values of the bounded array, dimensioned at 5.

 

Back to top

 

 

 

AUTOLOGOFF

 

To autologoff after an hour of inactivity, place code in events as follows:

 

           1. application idle event: gnv_app.event pfc_idle()                

           2. appmanager contructor event: Idle(3600)

           3. appmanager pfc_idle event: halt close

 

If you are familiar with the workings of the Idle event, then you could have it start a timer on your frame window in PB5, 

and there is a timer NVO in PFC 6.0. Both of these will trigger a timer event after the specified time length has expired. 

 

Method 1:

Quick & dirty: log out any open transactions, and call a HALT CLOSE. This will force the application to close 

immediately without any further processing. If you want open windows to be able to perform specific processing 

before the app closes, then try something like Method 2.

 

Method 2:

Since you are using PFC, you could use the sheetmanager application service to pull a list of open sheet windows. 

Inherit all of them from an application sheet ancestor and add a custom user event that will be triggered on all open 

windows. Something like ue_IdleTimeout(). It will need a return code so the calling script will know when all the 

processing is complete.

 

Instead of using an ancestor application sheet, you could also code this in w_master if you are using a copy of the 

PFE layer for your application. Using w_master would allow you to include any open response windows, etc., 

though you have to figure out how get a reference to the non-sheet windows. Some tweaking to the sheetmanager 

service might work.

 

In the window you declare the event, add the following script:

 

                ib_disableclosequery = True

 

This is a PFC instance variable that will disable all closequery and pfc_Save processing. Add any other globally 

required logic. Then, in the rest of your windows down the inheritance chain, add any other specific logic that is 

necessary. Once this event has been triggered on all open windows, close all transactions, and perform a HALT 

CLOSE just to make sure something didn't get missed

 

Back to top

 

 

 

BITWISE AND - ALTERNATIVE

 

Alternative to pfc's of_BiwiseAnd ().  faster than pfc's.  Here is the comparison (50 calls):

this:  . 1948 secs pfc version     .7168 sec

 

// Function:  of_BitWiseAnd (UnsignedLong aul_value1,UnsignedLong aul_value2)

// Description:   ANDs each bit of the values. this is an iterative function.

//   al_Value1 The first value to be used in the operation.

//   al_Value2 The second value to be used in the operation.

// Returns:   UnsignedLong The result of the AND operation.

// Author: Lijun Yang Date:  4/8/97

 

  UnsignedLong lul_return = 0

  Unsignedlong  lul_multiple = 1

 

  // Check for nulls

  If IsNull(aul_Value1) Or IsNull(aul_Value2) Then

   SetNull(ll_Result)

   Return ll_Result

  End If

 

  // Perform the AND

  Do

    lul_return +=  mod (aul_value1, 2) * mod (aul_value2, 2) * lul_multiple

    aul_value1 = aul_value1 / 2

    aul_value2 = aul_value2 / 2

    lul_multiple *= 2

  Loop Until aul_value1 = 0 AND aul_value2 = 0

 

  RETURN lul_return

 

 

Back to top

 

COLOURS

 

Colors in Windows are stored as 4 byte longs, with one byte each representing the Red, Green, and Blue 

components and fourth byte usually indicating (alpha) transparency. The structure is: 0xAABBGGRR 0x is 

hexadecimal zero, where AA is the alpha (usually 0), BB is the blue component, GG is Green, and RR is red. 

0x00 represents no colour, 0xFF saturation You can easily calculate the colors by the following two methods:

 

1) Use the Windows Calculator. Set the mode to Scientific (View->Scientific). Select Decimal (Dec) mode. 

Enter the number you have, then change to Hex mode.

 

2) Use math. R = mod (Colorvalue, 256); G = mod (Colorvalue /256, 256); B = mod (Colorvalue/65536, 256)

 

The 16 basic colours are listed in Help/RGB. Other colors of note are:

   Buttonface 79741120

   WindowText 41943040

   WindowBackground 1088479456

   Transparent 536870912

   CONSTANT long   BLACK = 0

   CONSTANT long   WHITE = 16777215

   CONSTANT long   LIGHTGREY = 12632256

   CONSTANT long   DARKGREY = 8421504

   CONSTANT long   RED = 255

   CONSTANT long   DARKRED = 128

   CONSTANT long   GREEN = 65280

   CONSTANT long   DARKGREEN = 32768

   CONSTANT long   BLUE = 16711680

   CONSTANT long   DARKBLUE = 8388608

   CONSTANT long   MAGENTA = 16711935

   CONSTANT long   DARKMAGENTA = 8388736

   CONSTANT long   BROWN = 8388736

   CONSTANT long   CYAN = 16776960

   CONSTANT long   YELLOW = 65535

   CONSTANT long   PALEYELLOW = 1226487

   CONSTANT long   WINDOWBACKGROUND = 1090519039

   CONSTANT long   BUTTONFACE =  79741120

   CONSTANT long   WINDOWTEXT = 33554432

   CONSTANT long   APPWORKSPACE = 276856960

Values for the upper color bits

  2^25 - Window Text

  2^26 - buttonface

  2^27 - scrollbar background

  2^28 - app. wrokspace

  2^29 - transparent

  2^30 - Window Background

  2^31 - unused

  2^32 - unused

 

Back to top

 

 

 

COMMAND LINE PARSING

 

string ls_cmd, ls_arg[]

integer i, li_argcnt

 

// Get arguments and strip blanks from start and end of string

ls_cmd  = Trim( commandParm () )

 

li_argcnt = 1

DO WHILE Len(ls_cmd ) > 0

        // Find first blank

        i =Pos( ls_cmd, " " )

        // If no blanks (only one argument), set i to point to the hypothetical character after end of string

        if i =0 then i =Len(ls_cmd) + 1

        // Assign the arg to the arg array.No. of chars copied is one less than position of the space found with Pos

        ls_arg[li_argcnt] =Left( ls_cmd, i - 1 )

        // Increment the argument count for the next loop

        li_argcnt = li_argcnt + 1

   // Remove the argument from the string so the next argument becomes first

        ls_cmd =Replace( ls_cmd, 1, i, "" )

LOOP

 

Back to top

 

 

 

CONNECTING TO MULTIPLE DATABASES

 

// enable transaction registration (the registration service is need when using more than one database)

gnv_app.of_SetTrRegistration(TRUE)

// register SQLCA (the connection to the first database which was connect to the usual way)

gnv_app.inv_trregistration.of_Register(SQLCA)

 

// connect to and register ABRN_MRS database (the second database)

// create 2nd transaction object; n_tr gtr2 must be declared global

gtr2 = CREATE n_tr  

// copy connection info from the first database called SQLCA to the second database

// called gtr2 - it copies the user id, password, database name, server name, etc. from

// the definition of the first database connection to the definition of the second database connection

il_return = SQLCA.of_CopyTo(gtr2) 

// re-assign the database name to the second definition

gtr2.Database = "your_2nd_database_name"

// re-assign the server name to the second definition - if it's different

gtr2.ServerName = "your_2nd_server_name"

// connect to the second database

ll_return = gtr2.of_Connect()

If ll_return <> 0 then MessageBox("Error", "Unable to connect.")

// register the second database with the registration service

gnv_app.inv_trregistration.of_Register(gtr2)

 

Now whenever you want to retrieve data from the 2nd database, you would write in the window's open event

  li_return = dw_1.of_SetTransObject(gtr2)

  dw_1.retrieve()

 

Back to top

 

 

 

DDLB AND DDDW EVENTS

 

There is an undocumented way to get to a lot of DDDW / DDLB events. Whenever an event occurs in the 

datawindowchild it sends a notification code to the parent DW.  This code can be intercepted in the user 

event mapped to pbm_command datawindow event.  If you want to experiment to determine which events 

you can intercept, try adding the code below to ue_command mapped to pbm_command:

 

mle_status.text += "hwndchild = " + String(hwndchild) + ", " + "childid = " + String(childid) + ", " + &

                                "notificationcode = " + String(notificationcode) + "~r~n"

 

The code needed to intercept the dropdown arrow in the ddlb is:

 

int li_rc

li_rc = this.GetChild("ddlbtest", ldwc_ddlb)

IF childid = Handle(ldwc_ddlb) THEN

CHOOSE CASE notificationcode

CASE 1

Post Event ue_DDLBRowFocusChanged()    

                END CHOOSE

END IF

 

Other events:

     Event                                 ActionID from WordParm

     Clicked                                             1281

     RowFocusChanged                      2048, 2049

     RightMouseButtonDown               2314

     Left Button Up                                 2313

     Retrieve End                                    769

     MouseMove                                     2311

 

Back to top

 

 

 

DDDW CALCULATOR AND CALENDAR

 

To use the  Drop Down Calendar or the Drop Down Calculator with the editmask style column you will need to use 

the "NONE" style service option.

 

1. Create a computed field with a bitmap() expression to the right of the datawindow column:

   BitMap("calendar.bmp")

where "calendar.bmp" may be a small calendar or a drop down arrow. Give the computed field a name; 

say "ship_date_arrow".

 

2. Enable the DDCalendar service with the "NONE" option:

  This.of_SetDropDownCalendar(TRUE)

  This.iuo_calendar.of_SetInitialValue(FALSE)

  This.iuo_calendar.of_SetAlwaysRedraw(FALSE)

  This.iuo_calendar.of_SetCloseOnClick(TRUE)

  This.iuo_calendar.of_SetCloseOnDClick(TRUE)

This.iuo_calendar.of_Register("ship_date",iuo_calendar.NONE)

 

3. Add code to the DW clicked event to drop the Calendar:

  IF dwo.name = "ship_date_arrow" THEN

       IF IsValid(iuo_calendar) THEN

               this.SetColumn("ship_date")

               this.event pfc_ddcalendar()

       END IF

  END IF

 

Back to top

 

 

 

DEPLOYMENT - DLLS

 

5.X

 

It is mandatory to have the deployment kit on your client machine. It doesn't matter if you are building machine code or 

p-code executable. In either case you need deployment kit. But you may reduce the number of DLLs to be deployed 

depending on what features you used in your application. Following gives the list of DLLs which are optional. For 

more information see Powersoft Document 4427.

 

All Platforms

pbbgr050.dll   Business graph engine

pbdwe050.dll  DW engine

pbrte050.dll    Runtime engine

pbrtf050.dll     Runtime functions

pbshr050.dll   Stg mgr, dec, print, ...

pbtyp050.dll   System object/function definitions

pbitxt50.dll     DW Import text (optional)

 

Win16, Win32

pbroi050.dll   OLE 2 support

pbtra050.dll   Database interface - trace of any database

pbdbl050.dll   Database interface - msg handler for pbsyb, pbmdi, pbnet (optional)

pbdbt050.dll   Database interface - msg handler for pbsyc (optional)

pbdpb050.dll  Dist PB - local driver (optional)

pbdse050.dll  Distributed PB (optional)

pbidbf50.dll    DW Import Dbase (optional)

pbin5050.dll   Database interface - Informix 5.0 (optional)

pbmdi050.dll  Database interface - MDI (optional)

pbmss050.dll Database interface - MS SQL Server 6.0 (optional)

pbo71050.dll  Database interface - Oracle 7.1 (optional)

pbodb050.dll  Database interface - ODBC (optional)

pbor7050.dll   Database interface - Oracle 7.0 (optional)

pborc050.dll   ORCA (optional)

pbosc050.dll  Dist PB - open server client (optional)

pbrtc050.dll    Rich text support (optional)

pbsmi050.dll   Dist. PB (optional)

pbsyb050.dll   Database interface - Sybase dblib interface (Microsoft lib linked) (optional)

pbsyc050.dll   Database interface - Sybase ctlib interface (optional)

pbwsc050.dll   Dist PB - winsock client (optional)

 

Win32

 

pbwss050.dll  Dist PB - winsock server (optional)

pbnpc050.dll   Dist PB - named pipe client (optional)

pbnps050.dll   Dist PB - named pipe server (optional)

pbo72050.dll   Database interface - Oracle 7.2 (optional)

pboss050.dll   Dist PB - open server server (optional)

pbsyt050.dll    Database interface - Sybase dblib interface (Sybase lib linked) (optional)

PFCCOM32.DLL – required for PFC apps

 

Win16

 

pboui050.dll  OLE 2 user interface

pbvbx050.dll  VBX

pbibm050.dll  Database interface - IBM (optional)

pbnet050.dll   Database interface - net gateway (optional)

And! -- PFCCOMM.DLL, PFCFLSRV.DLL

 

 

6.5

 

pbdwe60.dll         datawindow engine

pbmss60.dll        Microsoft direct driver - replace with

"pbsyt60.dll"

pbrtc60.dll             run time library

pbtra60.dll            database trace library (useful for live user

debugging!)

pbvm60.dll            main virtual machine

PFCCOM32.DLL  pfc printing library

PFCCOMM.DLL    ditto

PFCFLSRV.DLL   pfc file services library

PBODB60.DLL     ODBC connection

PBODB60.INI        ditto

 

Back to top

 

 

 

DIRECTORY EXISTS

 

Use the FileExists() function, and look for the file named '.' in the desired directory.  If the directory exists, it will 

contain '.' and '..' as files.

 

Back to top

 

 

 

DIRECTORY FILE LIST WITHOUT A WINDOW

 

                string ls_files[]

                window lw_1

                listbox llb_1

                int li_items, li_i

 

                Open( lw_1 )

                lw_1.openUserObject( llb_1 )

                llb_1.DirList( sFileSpec, uFileType )

                li_items = llb_1.TotalItems()

                For li_i = 1 to li_items

                        ls_files[ li_i ] = llb_1.Text( li_i )

                Next

                lw_1.closeUserObject( llb_1 )

                Close( lw_1 )

 

Back to top

 

 

 

DISABLE CLOSE

 

Alt-F4 is disabled if you remove the close option from  the system menu for the window. To do this, use this 

code in the open event of the window where you want to disable Alt-F4:

 

   UInt lUI_menuHandle

   lUI_menuHandle = GetSystemMenu( Handle( this ), FALSE )

   DeleteMenu( lUI_menuHandle, 61536, 0 )

 

 You will have to declare the Windows API calls as Local External  Functions of the window:

 

   FUNCTION uint GetSystemMenu( uint hWnd, boolean fRevert ) LIBRARY "USER"

   FUNCTION uint DeleteMenu( uint hMenu, uint idItem, uint fuFlags ) LIBRARY "USER"

 

Back to top

 

 

 

DOT NOTATION - LIMITATIONS IN 5.0.0x

 

In 5.0, dot notation is limited to accessing the first 32K rows in a datawindow or datastore.  GetItem( ) and SetItem( ) 

work fine regardless of the number of rows. PowerSoft is aware of this limitation, but no fix is expected until 6.0 due 

to the nature of the underlying bug. It will never be fixed in 5.0.

 

Back to top

 

 

 

DW - CHANGING THE PRESENTATION STYLE

 

In the exported .srd file search for the word "PROCESSING" - this is the key word that defines the appearance of the 

DataWindow. These are processing property values:

             0  (Default) Form, group, query, or tabular

             1  Grid

             2  Label

      3  Graph

             4  Crosstab

             5  Composite

             7  RichText

 

Back to top

 

 

 

DW – COLUMN HIGHLIGHTING OF THE CURRENT ROW

 

In the datawindow painter, double-click on the column in question and click on the tab mark "Expressions". 

Add this code to the background.color property

 

        if(currentRow() = getrow(), rgb(0,255,0), rgb(255,255,255))

 

or …

 

In the rowfocuschanged event for the datawindow control use this example:

// currentrow is a event argument

   Modify ("<colname>.background.color='0~tif(" + String (currentrow) + " = GetRow (),rgb(0,255,0),rgb(255,255,255)) '")

// OR dot notation:

dw_sheet.object.<colname>.background.color="0~tif (CurrentRow() = GetRow(),rgb(0,255,0),rgb(255,255,255))"

 

Back to top

 

 

 

DW COLUMN – SETTING VALUE OF COMPUTED COLUMN

 

You can’t. You can only set the expression. Using this expression, PB calculates and displays the value for all rows. 

If you need a blank string column to set a value for each row, you need to use a dummy column in your select 

statement like

 

                Select EmployeeID, FirstName, LastName, ..., space(10) "DummyString" From Employee

 

This last column "DummyString" becomes a normal column in your DataWindow like other columns EmployeeID etc. 

Remember to set it as Non-updatable column in Update properties. Now you can use this column to set different 

value for each row.

 

Back to top

 

 

 

DW - REFERENCING COMPUTED FIELDS

 

When you refer to an object in a band other than the detail band (usually a computed field) you must still specify a row number.

 

                Referenced object                               Row reference

                Header, footer, or summary              1

                Group header or trailer,                      Group no., eg : dw_1.Object.group_one[ 1 ]

 

If you specify nothing after the computed field name, you refer to the computed field dwObject, not the data.

 

For a computed field which occurs more than once, you can get all values by specifying buffer or datasource instead of row number, just as you can for columns.

 

When the expression returns an array (because there is no row number), you must assign the result to an array, even if you know there is only one row in the result.

 

Back to top

 

 

 

DW - FINDING COLUMN NAMES AT RUNTIME

 

  string sColNames[]

  int i

  FOR i = 1 TO Integer( dw_1.dwDescribe( "DataWindow.Column.Count)" )

sColNames[ I ] = <dwReference>.describe( "#" + string( i ) + ".name"  )

  NEXT

 

Back to top

 

 

 

DW - FINDING CONTROLS AT RUNTIME

 

  Integer li_obj, li_max_objects

  string ls_classname

 

  li_max_objects = UpperBound( windowname.Control )

  FOR li_obj = 1 TO li_max_objects

    CHOOSE CASE windowname.Control[ li_obj ].TypeOf()

CASE Datawindow!

            datawindow l_dw

            l_dw = windowname.Control[ li_obj ]

            ls_classname = l_dw.ClassName( )

        CASE ELSE // other window object types

    END CHOOSE

  NEXT

 

Back to top

 

 

 

DW – PREVENTING FOCUS LOSS ON TABBING FROM LAST ROW OR COLUMN

 

(Sandy Barletta[TeamPS] In the loseFocus event for the DW, check to see if focus is on the last column of the last 

row. If it is, then insert a new row, do a setcolumn onto the first column, do a setrow and scrolltorow, and POST a 

setFocus back to the DW.  That should do it. Example:

 

                //  DW LoseFocus event 

                LONG    ll_NewRow

 

                IF This.GetRow() = This.RowCount() AND This.GetColumnName() = 'mylastone' THEN

                        ll_NewRow = This.InsertRow(0)

                        This.SetColumn('myfirstone')

                        This.SetRow(ll_NewRow)

                        This.ScrollToRow(ll_NewRow)

                        This.POST SetFocus()

                END IF

 

Back to top

 

 

 

DW – CURRENTROW() AND GETROW()

 

What the datawindow or Infomaker function GetRow() returns depends on where it is called

 

                Called From                                         Returns

                Header                                                  First row on page

                Group header                                       First row in group

                Detail                                                     Row in which expression occurs

                Group trailer                                         Last row in group

                Summary                                              Last row in report or DataWindow

                Footer                                                    Last row on the page

 

In contrast, CurrentRow() returns the no. of the row which currently has focus.

 

In PowerScript however, GetRow() returns the number of the current row. Just to increase the confusion, we also have:

 

                SelectRow( lRow, lBool )   Highlight or unhighlight lRow in dw

                GetSelectedRow( lRow )    Reports no. of next selected row after lRow

                SetRow( lRow )                    Move cursor to lRow in dw but does not scroll

 

Back to top

 

 

 

 

DW GETROW() BUG FIX

 

Replacement for the regular GetRow Function  This version fixes the problem where the current row returned by 

GetRow can be greater than RowCount  This is a bug in PB that is usually encountered when the last row is deleted 

and the remaining rows are protected

 

                long    ll_row, ll_rowCount, ll_rowTarget

                int     li_rc

               

                ll_row = this.GetRow()

                ll_rowCount = this.RowCount()

 

                if ll_rowCount <= 0 then

                        return ll_rowCount

                else

                        if ll_row > ll_rowCount then

                                ll_rowTarget = ll_rowCount

                        else

                                if ll_row = 0 then

                                        ll_rowTarget = 1

                                        li_rc = this.ScrollNextRow( )

                                else

                                        return ll_row

                                end if

                        end if

                end if

 

                li_rc = this.SetRow( ll_RowTarget )

                // PB BUG

                if this.GetRow() <> ll_rowTarget then this.fu_scrollToRow( ll_rowTarget )

                this.fu_scrollToRow( ll_rowTarget )

               

                return ll_rowTarget

 

Back to top

 

 

 

DW GRIDS, CHANGING

 

With GRID datawindows, after you have created your initial datawindow, you can't switch columns easily as you can with 

other datawindow types. However, you can do this at runtime by clicking on a columnheader, and dragging it to another 

position. If you put the datawindow in preview mode, you are also able to do this. When going back to design mode, you'll 

see that the changes are reflected to the design as well. Neat and very handy if you know this. Everything you do in preview 

mode will be reflected in design mode. You can use this feature for resizing columns, moving columns, even moving 

columns underneath eachother.

 

Back to top

 

 

 

DW - GROUP BY N

 

Create a group for the DW that groups by the expression Long((GetRow()-1)/N) where N is the number you want to define your group by. Put the sum in the group footer as usual.

 

Back to top

 

 

 

DW HORIZONTAL LINES EVERY <N> ROWS

 

Add a line to the detail band, as low as possible; stretch it to the maximum width of your datawindow. Righ click on it and enter the following expression for the visible attribute:

 

                if ( mod ( getrow(), N ) = 0, 1, 0 )

 

where N is the number of rows between the lines.

 

Back to top

 

 

 

DW RETRIEVAL ARGUMENT USING AGGREGATE FUNCTION

 

In the dw SQL syntax painter, you can specify a subselect in the 'value' portion of the where clause.  Right mouse click 

on the value portion and choose the Select option.  This will open up another SQL Syntax painter where you can put 

together another select statement. Choose the employee table, but don't select any columns.  Just enter "min(id)" 

(without quotes) in the computed columns tab. When you return to the original dw SQL syntax, you'll be set up with a 

sql statement that will retrieve info about the employee with the smallest id.

 

Back to top

 

 

 

DW UPDATE ERROR,  ROW CHANGED BETWEEN RETRIEVE AND UPDATE

 

The 'Row Changed between Upd & Ret' error happens because of one the following:

 

1. Someone else changed the database row.

2. You changed the database row.

3. You changed the update status flags on the record.

4. You changed a value in the where clause.

 

If you are running single-user, (1) is out. If you are not calling setItemStatus(), then (3) is not the cause.  

Check to see if you might be deleting the row. 

 

On (4):  if your where criteria is to use key and modified values, or key and updateable values, if you change any of those 

original values through any other means, when you update the datawindow, it will not find the row. Use the PFC SQL 

Spy service (or check the sql statement argument of the sqlpreview event of the datawindow when you are updating it) to 

see the SQL statement being sent to the database.  This will undoubtably show you what value is causing the row not to 

be found and will thus lead you to the place in code where you are changing the value.

 

Back to top

 

 

 

ENTER KEY – MAKE IT EMULATE THE TAB KEY

 

To have the enter key act like a tab key, create a user event to correspond with the pbm_dwnprocessenter event on a

datawindow. In that user event, add the following 2 lines:

 

  Send( Handle( this ), 256, 9, Long( 0, 0 ) )

  <dwRef>.SetActionCode( 1 )

 

Back to top

 

 

 

ENVIRONMENT VARIABLES

 

For getting the value of environment variables, like PATH:

  function long GetEnvironmentVariableA(string lpszName, ref string lpszValue,  long dwValue) library "kernel32.dll"

 

You must pre-allocate the string <lpszValue>. (eg 2k)

 

// If you need to set an enviroment variable:

  function int SetEnvironmentVariableA(string lpszName, ref string lpszValue) library "kernel32.dll"

 

Back to top

 

 

 

FINDFIRSTFILE API

 

$PBExportHeader$nvo_kernell32.sru

$PBExportComments$Kernell32 API functions

forward

global type nvo_kernell32 from nonvisualobject

end type

end forward

 

// THIS MUST BE DONE IN CODE -- STRUCTURE PAINTER DOES NOT SUPPORT IT

type str_filetime from structure

   ulong ul_LowDateTime

   ulong ul_HighDateTime

end type

 

type str_win32_find_data from structure

        unsignedlong            fileattributes

        str_filetime            creationfile

        str_filetime            lastaccesstime

        str_filetime            lastwritetime

        unsignedlong            filesizehigh

        unsignedlong            filesizelow

        unsignedlong            reserved0

        unsignedlong            reserved1

        character               filename[260]

        character               altfilename[14]

end type

 

global type nvo_kernell32 from nonvisualobject autoinstantiate

end type

 

type prototypes

FUNCTION ulong FindFirstFile(ref string lpszSearchFile, ref

STR_WIN32_FIND_DATA lpffd)  LIBRARY "kernel32.dll" ALIAS FOR "FindFirstFileA"

 

FUNCTION boolean FindNextFile(ulong hfindfile, ref STR_WIN32_FIND_DATA lpffd)  LIBRARY "kernel32.dll" 

ALIAS FOR "FindNextFileA"

end prototypes

 

type variables

// MAX_PATH constant for file operations

Private constant long MAX_PATH = 260

end variables

 

forward prototypes

public function integer of_dirtoarray (string path, ref string lista[])

end prototypes

 

public function integer of_dirtoarray (string path, ref string lista[]);long

lul_handle

str_win32_find_data str_find

boolean lb_fin

integer n

 

str_find.filename=space(MAX_PATH)

str_find.altfilename=space(14)

n = 0

lul_handle = FindFirstFile(path, str_find)

do

        n += 1

        lista[n] = str_find.filename

        lb_fin = FindNextFile(lul_handle, str_find)

loop while lb_fin

 

return n

 

end function

on nvo_kernell32.create

TriggerEvent( this, "constructor" )

end on

 

on nvo_kernell32.destroy

TriggerEvent( this, "destructor" )

end on

 

Example:

 

string ls_lista[]

integer i,n, li_fn

nvo_kernell32 inv_kernell

 

li_fn = FileOpen("c:\temp\dll.txt", LineMode!, Write!, LockWrite!, Replace!)

ls_lista[1] = ''

n = inv_kernell.of_dirtoarray("c:\windows\system\*.dll", ls_lista)

for i=1 to n

        FileWrite(li_fn, ls_lista[i])

next

FileClose(li_fn)

 

Back to top

 

 

 

FREE DISK SPACE

 

If you want the amount of used/free memory then use GlobalMemoryStatus(). If you want the amount of used/free disk 

space then use GetDiskFreeSpaceA().

 

GlobalMemoryStatus:

 

     type str_memorystatus from structure

        ULong ul_Length;        // size of str_memorystatus

        ULong ul_MemoryLoad;    // percent of memory in use

        ULong ul_TotalPhys;     // bytes of physical memory

        ULong ul_AvailPhys;     // free physical memory bytes

        ULong ul_TotalPageFile; // bytes of paging file

        ULong ul_AvailPageFile; // free bytes of paging file

        ULong ul_TotalVirtual;  // user bytes of address space

        ULong ul_AvailVirtual;  // free user bytes

     end type

 

     Subroutine GlobalMemoryStatus(Ref str_memorystatus astr_memorystatus) &

        Library "KERNEL32.DLL";

 

GetDiskFreeSpaceA:

 

     Function Boolean GetFreeDiskSpace( &

        Ref String as_RootPathName,           & // address of root path

   Ref ULong aul_SectorsPerCluster,     & // number of sectors per cluster

        Ref ULong  aul_BytesPerSector, & // number of bytes per sector

        Ref ULong  aul_NumberOfFreeClusters, & // number of free clusters

        Ref ULong  aul_TotalNumberOfClusters) & // total number of clusters

      Library "KERNEL32.DLL" &

      Alias For GetFreeDiskSpaceA;

 

     The calculations are:

 

          Free Disk Space  = aul_NumberOfFreeClusters * aul_SectorsPerCluster * aul_BytesPerSector

          Total Disk Space = aul_TotalNumberOfClusters * aul_SectorsPerCluster * aul_BytesPerSector

          Used Disk Space  = Total Disk Space - Free Disk Space

 

GetDiskFreeSpaceA() works correctly only with hard drives less than 2M. For large disks you should use

 

 GetDiskFreeSpaceExA ( LPCTSTR lpDirectoryName,

                                              PULARGE_INTEGER lpFreeBytesAvailableToCaller,

                                              PULARGE_INTEGER lpTotalNumberOf Bytes,

                                PULARGE_INTEGERlpTotalNumberOfFreeBytes )

 

which is available only in Windows NT and Windows 95 (OSR 2). Documented in Windows SDK

 

Back to top

 

 

 

HEX STRINGS

 

// string f_dec_to_hex ( integer aiNumber), 0 <= n <= 15

// Converts integer from 0 - 15 into character hex representation

string sHexChar

Choose Case aiNumber

        Case 10

            sHexChar = 'A'

        Case 11

                sHexChar = 'B'

        Case 12

                sHexChar = 'C'

        Case 13

                sHexChar = 'D'

        Case 14

                sHexChar = 'E'

        Case 15

                sHexChar = 'F'

        Case Else

                sHexChar = String(aiNumber)

End Choose

Return sHexChar

 

// string f_convert_to_hex( long alNumber ), recursive:

// Recursive function to translate number into hex representation

If alNumber > 15 Then

        Return f_convert_to_hex( alNumber / 16 ) + f_dec_to_hex( Mod( alNumber, 16 ) )

Else

        Return f_dec_to_hex( alNumber )

End If

 

 

// string f_convert_char_to_hex( string asString )

// Converts an input string into a hex character string

string sResult, sHexChar

integer i

For i = 1 to Len( asString )

        sHexChar = f_convert_to_hex( Asc( Mid( asString, i, 1 ) ) )

        If Len( sHexChar ) = 1 Then sHexChar = "0" + sHexChar

        sResult = sResult + sHexChar

Next

Return sResult

 

Back to top

 

 

 

LOGIN - GETTING NETWARE LOGIN NAME

 

In one of the example pbl's that comes with 5.0 (PBEXAMFE.PBL), there is a user object called u_external_function_win32. 

This nvo has a uf_get_logon_name() function that invokes a call to GetUserNameA, which resides within ADVAPI32.DLL.

 

     string ls_LocalName

     string ls_RemoteName

     int i_iX

     uint ui_cb

 

     FOR i_iX = 1 to 26

          ls_LocalName = char ( 64 + i_iX ) + ":"

          ls_RemoteName = space(128)

          ui_cb = 127

          IF WNetGetConnectionA( ls_LocalName, ls_RemoteName, ui_cb ) = 0 THEN

                This.AddItem ( ls_LocalName + "~t- " + ls_RemoteName)

          ELSE

               IF ii_bShowNotConnected THEN

                    This.AddItem ( ls_LocalName + "~t-not connected" )

               END IF

        END IF

     NEXT

 

Back to top

 

 

 

LOGIN - NETWARE

 

FUNCTION UINT WNetGetConnectionA( ref string ln, ref string rn, ref uInt cb ) library "mpr.dll"

 

Back to top

 

 

 

MAIL - SENDING FROM PB APP

 

// ue_sendmail

// mailSession nonvisual object signs on and establishes a MAPI session.

 

Int     li_Return

String ls_email_address, ls_attach_name

mailSession mSes

 

/*

  mailReturnCode. Returns one of the following values:

                mailReturnSuccess!,

                mailReturnLoginFailure!

                mailReturnInsufficientMemory!

                mailReturnTooManySessions!

                mailReturnUserAbort!

                If any argument's value is NULL, mailLogon returns NULL.

 */

mailReturnCode mRet

 

// mailMessage obj is a system struct with info about a specific mail message. A mailMessage object has no events.

mailMessage mMsg

 

// mailFileDescription obj is a system struct with info about attachment file to a mail message. no events.

mailFileDescription mAttach

 

// The attached file to be saved as a *.psr Format

ls_attach_name = gnvo_constants.is_Temp_Dir + 'DataWndw.psr'

 

// Create a mail session

mSes = create mailSession

 

// Get user to enter email loginid and password

Open(w_email_login)

 

// Log on to the session

//mRet = mSes.mailLogon ("userid","password")

mRet = mSes.mailLogon ( is_email_loginID , is_email_password)

IF mRet <> mailReturnSuccess! THEN

                //MessageBox ("Mail", 'Logon failed.  Make sure the email's MAPI server is started.')

                MessageBox ("Mail", 'Logon failed.')

                RETURN

END IF

 

//The datawindow to be saved as ls_attach_name, which is the *.psr file.

li_Return = tab_1.tabpage_main.dw_10.saveas(ls_attach_name,PSReport!,True)

 

//Specifies the content of the message body. (Runtime only.)

mMsg.NoteText = 'Universal Result Reporting Email, check the attachment:'

 

//Specifies the recipient of the current message.

//mMsg.Recipient[1].name = 'Lastname, Firstname'

Open( w_email_address )

ls_email_address = Message.StringParm

mMsg.Recipient[1].name = ls_email_address

//mMsg.Recipient[1].name = 'Fred@acme.com'

 

//Specifies the type of the attachment file. Values are:mailAttach! (data file)

mAttach.FileType = mailAttach!

//Specifies the full path of the attachment file.

mAttach.PathName = ls_attach_name

//Specifies the full path of the attachment file.

mAttach.FileName = ls_attach_name

 

// Specifies the position of the attachment file within the message body. (Read only.)

// Note: In MS Mail v3.0b, Position=-1 puts attachment at the beginning of the message.

// This will place the attachment at the End of the text of the message

mAttach.Position = len(mMsg.notetext) - 1

// Specifies the file attachment for the current message.

// A mailFileDescription array contains information about an attachment file.

mMsg.AttachmentFile[1] = mAttach

 

// Send the mail

mRet = mSes.mailSend ( mMsg )

 

IF mRet <> mailReturnSuccess! THEN

                MessageBox("Mail Send", 'Mail not sent')

                RETURN

END IF

 

// End the mail session, breaking connection between the PowerBuilder application and mail. If the mail

// app was already running when PowerBuilder began the mail session, it is left in the same state

mSes.mailLogoff()

 

//DESTROY the Mail session that was created with the CREATE statement.

DESTROY mSes

 

Back to top

 

 

 

MESSAGE OBJECT - EXTENDING

 

You can extend the message object by completing the following steps:

 

Using the PFC:

        1) Go to your Application Painter

        2) Open the Application's 'Properties' window.

        3) Click on the 'Variable Types' tab

        4) Change the Message attribute from 'message' to 'n_msg'

        5) Add any necessary functions/instance variables to n_msg.

       

Not using the PFC:

        1) Create a 'Standard' Non-visual user object of type 'message'

        2) Name it 'n_message' or whatever you like.

        3) Go to your Application Painter

        4) Open the Application's 'Properties' window.

        5) Click on the 'Variable Types' tab

        6) Change the Message attribute from 'message' to 'n_message'

        7) Add any necessary functions/instance variables to n_message.

 

These instance variables that you add to the message object become yours to reference whenever you like and you 

don't have to worry about PB stepping on their values.  All you have to worry about is one of your developers or yourself 

stepping on those values.

 

The message object is very short lived. Since it's global and used by PB as well, what you pass in it is not guaranteed 

to be that way for long. For this reason, it is often been suggested to override the open event of a PFC window, grab the 

object/value you need from the message object, then call the ancestor (to trigger the pfc_preopen and post the pfc_postopen 

events).As for how to how to grab what you want so it's there when you need it later, create a local instance of the object and 

copy all the attributes over. The best way to do this is to add a function on your object called something like 

of_duplicateSelf(). It should take an argument of the same type as itself by reference and copy everything over eg: 

     function integer u_myObject.of_duplicateSelf(reference u_myObject au_myObject)

          au_myObject.is_x = this.is_x

          au_myObject.ib_y = this.ib_y

          au_myObject.ii_z = this.ii_z

          return 1

     end function

 

Then, to use it...

 

    u_myObject     lu_temp

    u_myObject     lu_myObject

 

     lu_temp = message.powerobject // Cast it to type u_myObject

     lu_myObject = create u_myObject

     lu_temp.of_duplicateSelf(lu_myObject)

     destroy lu_myObject

 

Back to top

 

 

 

MOUSE POSITION

 

Finding the mouse position relative to the current window, the frame etc. Create a structure as follows and save as 

s_mouselocation

 

                xposition   ulong

                yposition   ulong

 

Declare this external function

 

FUNCTION boolean GetCursorPos(ref s_mouselocation mousepos) LIBRARY "User32.dll"

 

Place the following code in clicked event script of a window

 

  s_mouselocation lstr_mouseloc  // var of structure type

  boolean lb_retval

  lb_retval = GetCursorPos( lstr_mouseloc )

  if lb_retval=true then

        MessageBox("Cursor Position", "X = " + string(lstr_mouseloc.xposition) + " Y = "&

                                        + string(lstr_mouseloc.yposition))

        MessageBox("Cursor Position", "X = " + string(PixelsToUnits(PointerX(),XPixelsToUnits!)) + " Y = "&

                                        + string(PixelsToUnits(PointerY(),YPixelsToUnits!)))

  Else

      MessageBox("Error","Error finding the mouse position.")

  End if

 

Back to top

 

 

 

MULTI-LANGUAGE SUPPORT

 

For western-style alphabets ...

 

1. In your string table, associate a string id with actual string values for the language.

 

2. Place the id of the appropriate string in the tag field for every control and datawindow text object.

 

3.  In the open event of the window, loop through the window's control array.  For each control in the control array, load 

the string associated with the control's string id that is stored in its tag field and assign it dynamically at run time.  For 

each datawindow control, loop through the list of text objects and do the same thing.

 

You can easily encapsulate this behavior in a generic service object.  You can then call a function in the service object 

to replace all the strings at runtime.

 

This way, you do not have to have a separate window for each language. Because translated strings will have varying 

length depending on the language, you have to make sure that the text objects are big enough to accomodate the 

translation for each supported language. We have found that by making the fields 33% bigger than what is necessary 

for the English string, we can safely fit the translated string for most languages.

 

Back to top

 

 

 

NETWORK DRIVES

 

16bit: FUNCTION UINT WNetGetConnection( ref string ln, ref string rn, ref uint cb ) library "user.exe"

32bit: FUNCTION UINT WNetGetConnectionA( ref string ln, ref string rn, ref uint cb ) library "mpr.dll"

 

Use the following script in a listbox userobject, for instance in the constructor event. The instance variable

ii_bShowNotConnected lets you also display not connected drive letters. Default, set it to FALSE.

 

                string ls_LocalName

                string ls_RemoteName

                int i_iX

                uint ui_cb

                FOR iX = 1 to 26

                     ls_LocalName = char ( 64 + iX ) + ":"

                     l_sRemoteName = space(128)

                     ui_cb = 127

                     IF WNetGetConnectionA( ls_LocalName, ls_RemoteName, ui_cb ) = 0 THEN

                          This.AddItem ( ls_LocalName + "~t- " + ls_RemoteName )

                     ELSE

                          IF ii_bShowNotConnected THEN

                               This.AddItem ( ls_LocalName + "~t- not connected" )

                          END IF

                     END IF

                NEXT

 

Back to top

 

 

 

 

PFC DDDW - MULTIPLE ROWS FROM A LOOKUP

 

METHOD 1: use ia_returnVal[] as you would for a single row, but keeping appending values for each selected row.

 

                long ll_row, ll_counter

 

                ll_row = dw_master.getSelectedRow(0)

                DO WHILE ll_row > 0

                       ll_counter++

                       inv_selectionAttrib.ia_returnVal[ll_counter] = dw_master.object.cust_id[ll_row]

                       ll_counter++

                       inv_selectionAttrib.ia_returnVal[ll_counter] = dw_master.object.cust_name[ll_row]

                       ll_row = dw_master.getSelectedRow(ll_row)

                LOOP

 

METHOD 2: Create a custom non-visual user object. Declare an instance variable of data type Any. Optionally you 

can set AutoInstantiate to True.  Declare the custom non-visual in your window (as an instance or locally in an event 

or window function)  Inside a function or an event you can use dot notation to populate the non-visual. Inside 

pfc_default event for the window you can issue a CloseWithReturn (inv_selectdata.ia_data)

 

Back to top

 

 

 

PFC HELP TOOLBAR BUTTON

 

Add a button to the PowerBar as follows:

 

  Right click on PowerBar.

  Choose "customize"

  Customize dialog box will appear

  Select icon from custom palette and drag it down to current toolbar in the position you want it to appear.

  Toolbar Item Command dialog box will appear.

  Type --> winhelp C:\pwrs\Pb5\Sys\pbpfc050.HLP in Command Line <substitute your path to file>

  Type label such as "PFC Help" in Item Text and Item Microhelp

  Click OK

  You now have a convenient toolbar button to bring up PFC help

 

Back to top

 

 

 

PFC NUMERIC SERVICE - HEX EXTENSIONS

 

Function: n_cst_numerical.of_bin2hex

Scope: protected object function

Prototype: string of_bin2hex (string as_binary);

 

// Object: n_cst_numerical

// Method: of_bin2hex

// Author: Boris Gasin 9/27/96

// Arg   : as_binary

// Return: string

// Desc  : converts a 4 bit binary to hex. used internally by of_hex

 

as_binary = Right( "000" + as_binary, 4)

 

CHOOSE CASE as_binary

        CASE "0000"

                return "0"

        CASE "0001"

                return "1"

        CASE "0010"

                return "2"

        CASE "0011"

                return "3"

        CASE "0100"

                return "4"

        CASE "0101"

                return "5"

        CASE "0110"

                return "6"

        CASE "0111"

                return "7"

        CASE "1000"

                return "8"

        CASE "1001"

                return "9"

        CASE "1010"

                return "A"

        CASE "1011"

                return "B"

        CASE "1100"

                return "C"

        CASE "1101"

                return "D"

        CASE "1110"

                return "E"

        CASE "1111"

                return "F"

        CASE ELSE

 return "?"

END CHOOSE

 

Function: dpt_n_cst_numerical.of_hex

Scope: public object function

Prototype: string of_hex (long al_long);

 

// Object: n_cst_numerical

// Method: of_hex

// Author: B. Gasin

// Date  : 5/21/97

// Arg   : al_long

// Return: string hex representation of the decimal number.If any argument's value is NULL, reurns NULL.

//      Description:   Determines the Hex representation of a Decimal number.

 

string  ls_hex = ""

string  ls_binary = ""

string  ls_4bit = ""

 

//Check parameters

If IsNull(al_long) or al_long< 0 Then

        string ls_null

        SetNull(ls_null)

        Return ls_null

End If

 

If al_long = 0 Then Return '0'

 

// Convert Dec to binary

ls_binary = of_binary(al_long)

IF IsNull(ls_binary) THEN return ls_binary

 

// Convert binary to hex

DO

        // get the first 4 bits from the right

        ls_4bit = Right(ls_binary, 4)

        // Convert to hex

        ls_hex = of_bin2hex(ls_4bit) + ls_hex

        // Trim the rest

        ls_binary = Left(ls_binary, Len(ls_binary) - 4)

LOOP Until ls_binary = ""

 

Return ls_hex

 

 

There is a bug in of_getBit() that truncates negative and >32K values.  Here is the fix.

 

Function: n_cst_numerical.of_getbit

Scope: public object function

Prototype: boolean of_getbit (long al_decimal,

       unsignedinteger ai_bit);

// Object: n_cst_numerical

// Method: of_getbit

// Author: B. Gasin

// Date  : 4/7/97

// Return: boolean

// Desc  : Override the PFC function to provide support for negative numbers and fix a bug that truncates numbers > 32k

 

Boolean lb_null, lb_negative, lb_result

 

//Check parameters

If IsNull(al_decimal) or IsNull(ai_bit) then

        SetNull(lb_null)

        Return lb_null

End If

lb_negative = (Sign(al_decimal) = -1)

IF lb_negative THEN al_decimal = Abs(al_decimal) - 1

//Assume ai_bit is nth bit counting right to left with leftmost bit being bit one. al_decimal is a binary number, base 10 long.

lb_resulkt = (Int(Mod(al_decimal /  2 ^(ai_bit - 1), 2)) > 0) Then lb_result = True Else lb_result = False

IF lb_negative THEN lb_result = NOT lb_result

return lb_result

 

Back to top

 

 

 

PFC TRANSACTIONS - OPTIMISTIC LOCKING, ONE OPINION

 

Datawindows use optimistic locking, which is to say, they assume it  will be unlikely two users will try to update the same 

record at the same time, so users can always read the row into the DW, and it will  be checked for concurrency at update 

time.  If someone else has  modified the row in the meantime, the DW will automatically notify the user and ask for a 

course of action to resolve the situation.

 

To catch the occasions where multiple users try to update the same  row, PB allows developers to choose several update 

options,  specifying what will be used in the WHERE clause to catch concurrent updates.  If you choose "Key Columns", 

only the key value(s) will be included in the WHERE clause and you effectively have no concurrency  detection. If you select 

"Key and Updateable Columns" or "Key and  Modified columns", those extra columns will be used to qualify the  UPDATE 

statement to catch situations where another user has updated  the row since it was read into the DW.  In many cases "Key 

and  Modified Columns" is the most desirable since it allow two users to update the same record as long as they don't try 

to change the same column.  If you want to prevent any concurrent updates, choose "Key and Updateable Columns".

 

 It is almost always a bad idea to use pessimistic locking such as you describe in real-life client/server applications.  Too much chance for client PC lockups or someone loading a screen then going for lunch, preventing anyone else from accessing that that row.

 

Back to top

 

 

 

PFC WINDOWS - PLACING CONTROLS

 

1. Though standard PowerBuilder native controls are displayed on the control palette, they should never be used in a PFC 

application. Never mix and match native controls with PFC controls. All controls must be inherited from a corresponding 

PFC control. For example, a command button has a corresponding standard visual user object u_cb. PFC controls notify 

the parent window every time they get focus. This becomes important when the message router is looking for the last 

control that had focus. The only icon that should be used on this palette is the one to select user objects. In a PFC 

environment, consider removing native controls in the window and user object painters.

 

2. All PFC controls are contained in the pfemain.pbl library. These controls are extensions from the PFC libraries. The 

visual controls are prefaced with a u_, and the non-visual controls, such as a datastore, are prefaced with a n_.

 

3. When placing user objects (controls) on your window be sure to select the user object icon on the Painter palette,  

not on the Powerbar.

 

Back to top

 

 

 

SET DEFAULT PRINTER FROM WITHIN PB

 

This code is limited to 32 bit programming since under 16 bit a system reboot would be required. Under 32 bit the 

registry effects the system defaults automatically allowing for dynamic changes at runtime. Remember that the printer 

you dynamically change to must already be configured on that system.

 

Win95 requires an extra step to setting a new default printer.

 

/******************************************************************

Variable Explaination:

li_rtn = Return code to verify if the WIN.INI update succeeded.

ls_default = String variable that contains the default printer name.

ls_driver = String variable that holds the printer's driver.

ls_port = String variable that holds the printer's port.

ls_printer = String that will contain the concatenated printer's driver and port.

ls_key = String variable containing the registry path to the printer information.

 

Object Explaination:

sle_def = Single line edit that contains the default printer's name.

sle_def2 = Single line edit that contains the default printer's port and driver.

sle_new = Single line edit that contains the new printer's name.

sle_new2 = Single line edit that contains the new printer's port and driver.

*******************************************************************/

//Variable declarations:

int li_rtn

string ls_default, ls_driver, ls_port, ls_printer, ls_key

 

//STEP 1: Get the current default printer name.

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

sle_def.text = ls_default

 

//STEP 2: Get the default printers driver and port.

ls_key ="HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Print\Printers\"+sle_def.text

RegistryGet(ls_key, "Printer Driver", ls_driver)

RegistryGet(ls_key, "Port", ls_port)

sle_def2.text = ls_driver + "," + ls_port

 

//STEP 3: Set a new default printer name.

int li_rtn

li_rtn =RegistrySet( &

"HKEY_LOCAL_MACHINE\Config\0001\System\CurrentControlSet\Control\Print\Printers", "default", sle_new.text)

if li_rtn = 1 then

   Messagebox("Setting new printer name", "Successful")

else

   Messagebox("Setting new printer name", "Failed")

end if

 

//STEP 4: Get the new default printer's driver and port.

ls_printer = sle_new.text

ls_key ="HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Print\Printers\"+ls_printer

RegistryGet(ls_key, "Printer Driver", ls_driver)

RegistryGet(ls_key, "Port", ls_port)

sle_new2.text = ls_driver + "," + ls_port

 

//STEP 5: Set the new default printer name, driver and port in the WIN.INI file.

ls_printer = sle_new.text + "," + sle_new2.text

li_rtn = SetProfileString("c:\windows\win.ini", "Windows", "device",ls_printer)

if li_rtn = 1 then

   Messagebox("Win.ini Update", "Successful")

else

   Messagebox("Warning", "An error has occurred")

end if

 

Back to top

 

 

Windows NT Code Example:

/*
  Set a new default printer name under Windows NT. More complicated.
  Windows NT stores printers as "name,driver,port" so we need to:
    1. Get a list of printers for the current user with their driver and port information
    2. Convert the names of network printers - Windows Printer Name "HP4  on SERVER" is actually "\\SERVER\HP4" in the registry
    3. Look up the printer we need - "HP 4Plus on SERVER"
    4. Store the correct name - "\\SERVER\HP 4Plus" - and driver,port in the registry as the default printer.
*/

integer iReturn, iPrinter, iPos, iNumPrinters
string sSystemName[], sWindowsName, sDriverPort, sPrinter

sPrinter = sle_1.text

// 1. Get a list of printers
iReturn = RegistryValues("HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Devices",  sSystemName)
if iReturn <> 1 then
  MessageBox("Error", "Unable to get list of Windows Printers.~r~n" + &
                                         "Unable to set " + sPrinter + " as the default printer")
  return
end if

iNumPrinters = upperBound(sSystemName)
for iPrinter = 1 to iNumPrinters
  if left(sSystemName[iPrinter], 2) = '\\' then
    // network name, convert to Windows name
    iPos = Pos(sSystemName[iPrinter], '\', 3)
    sWindowsName = Right(sSystemName[iPrinter], Len(sSystemName[iPrinter]) - iPos) + " on " +&
                                      Mid(sSystemName[iPrinter], 3, iPos - 3)
  else
    // Windows name, use as is
    sWindowsName = sSystemName[iPrinter]
  end if

  if sWindowsName = sPrinter then exit
next

if iPrinter > iNumPrinters then
  // Unable to find printer in list
  MessageBox("Error", "Printer " + sPrinter + " could not be found.~r~n" +  "Unable to set " + sPrinter + " as the default printer")
  return
end if
iReturn = RegistryGet("HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Devices", &
                                    sSystemName[iPrinter], sDriverPort)
if iReturn <> 1 then
  MessageBox("Error", "Unable to get printer details.~r~n" +  "Unable to set " + sPrinter + " as the default printer")
  return
end if

sDriverPort = sSystemName[iPrinter] + ',' + sDriverPort
iReturn = RegistrySet("HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows", "Device", sDriverPort)
if iReturn <> 1 then
  MessageBox("Error", "Unable to set " + sPrinter + " as the default printer")
  return
end if

Back to top

 

 

 

RMB MENUS

 

One thing most developers don't realise is that a right mouse button popup menu should be invoked by a right mouse 

]button UP event. (See how MS explorer does it) the right mouse button down event should be used to 

select/de-select/extend select etc of datawindow rows. Unfortunately PowerBuilder uses the rmbuttondown event 

to suppress its internal rmb popup menu.

 

So to prevent the automatic RMB menu (comes from PowerBulder not Win95) from appearing:

 

Create an event called rbuttonup and map it to the pbm_dwnrbuttonup event. Use the following code:

 

                // Display the RMB menu

                this.of_RMBPopUp(xpos,ypos,row,dwo)

 

In the rbuttondown event use the following code.

 

                // Prevent default popup menu

                return 1

 

Note: return 0 will cause the internal rmb popup menu to be displayed. (Paul Blackmore)

 

 

 

SQL/ORACLE PB BUG

 

Oracle uses the semi-colon as the statement terminator. Submitting a single SQL statement has ONE semi-colon 

appended to the end. Creating a trigger/function/procedure contains more than one SQL statement. Each statement 

is terminated with the semi-colon. If the SQL used in the Execute Immediate contains one or more carriage return/line 

feeds, then Powerbuilder, god knows why, searches for the semi-colon, and only submits that part of the SQL. If there 

are no carriage return/line feeds in the SQL, powerbuilder just sends the SQL. If I remove ALL carriage return/line 

feeds from the SQL, everything works fine regardless of the type of SQL. SELECT/UPDATE/CREATE etc.

 

Back to top

 

 

 

STORED PROCEDURE WITH OUTPUT PARAMETER

 

When executing a stored procedure with an OUT parameter, you can FETCH the result:

 

DECLARE Proc1 FOR GetAddrCity( :lAddressId )

EXECUTE Proc1 ;

FETCH lProc INTO :lsCity ;

CLOSE lProc ;

 

Back to top

 

 

TCP/IP ADDRESS

 

Structure: str_wsadata

   unsignedinteger  version

   unsignedinteger  highversion

   character description[257]

   character systemstatus[129]

   nsignedinteger  maxsockets

   unsignedinteger  maxupddg

   string vendorinfo

 

External function declarations:

  function int WSAStartup (uint UIVerionrequested, ref str_wsadata lpWSAdata)library "wsock32.DLL"

  function int WSACleanup() library "wsock32.DLL"

  function int WSAGetLastError() library "wsock32.DLL"

  function int gethostname(ref string name, int namelen) library "wsock32.DLL"

  function string GetHost(string lpszhost,ref blob lpszaddress) library "pbws32.dll"

 

PowerScript code:

 

String ls_ipaddress, ls_hostname

Blob{4} lb_hostadress

Integer li_version, li_rc

str_wsadata lstr_wsadata

 

ls_hostname = Space(128)

li_version = 257

 

If WSAStartup(li_version, lstr_wsadata) = 0 Then

   If GetHostName(ls_hostname, Len(ls_hostname)) < 0 Then

      li_rc = WSAGetLastError()

   Else

      GetHost(ls_hostname, lb_hostadress)

      ls_ipaddress  = String(Asc(String(BlobMid(lb_hostadress, 1, 1)))) + "."       

      ls_ipaddress += String(Asc(String(BlobMid(lb_hostadress, 2, 1)))) + "."

      ls_ipaddress += String(Asc(String(BlobMid(lb_hostadress, 3, 1)))) + "."

      ls_ipaddress += String(Asc(String(BlobMid(lb_hostadress, 4, 1))))

      li_rc = 0

   End If

Else

   li_rc = WSAGetLastError()

End If

 

WSACleanup()

 

Back to top

 

 

 

WINDOW EVENTS

Activate Just before the window becomes active
Clicked User clicks in an unoccupied area of the window (any area with no visible, enabled control)
Close When window is closed.
CloseQuery When you remove a window from display (close it)
Deactivate When window becomes inactive.
DoubleClicked When user double-clicks in an unoccupied area of the window (any area with no visible,  enabled object).
DragDrop When a dragged control is dropped on the window.
DragEnter Dragged control enters the window.
DragLeave Dragged control leaves the window.
DragWithin Dragged control is within the window.
Hide Just before the window is hidden.
HotLinkAlarm After a DDE server app has sent (changed) data and the client DDE application has received it.
Key

When user presses a key and the insertion point is not in RichTextEdit or DataWindow edit 

control. 

MouseDown

When the user presses the left mouse button in an unoccupied area of the window (any area with  

no visible,enabled object).

MouseMove When the pointer is moved within the window.
MouseUp

When user releases the left mouse button in an unoccupied window area  (any area with 

no visible, enabled object).

Open When a script executes the Open function for a window.
Other When a Windows message occurs that is not a PowerBuilder event.
RButtonDown When right mouse button is pressed in an unoccupied window area (any area with no  visible, enabled object).
RemoteExec When a DDE client application has sent a command.See the platform note at the beginning of   this section
RemoteHotLinkStart

When a DDE client application wants to start a hot link.

RemoteHotLinkStop When a DDE client application wants to end a hot link. 
RemoteRequest When a DDE client application requests data.
RemoteSend When a DDE client application has sent data.S
Resize When the user or a script opens or resizes a window.
Show When a script executes the Show function for this window. The event occurs just before the window is displayed.
SystemKey When the insertion point is not in a line edit and the user presses ALT or ALT + another key
Timer When a specified number of seconds elapses after the Timer function has been called.
ToolbarMoved In an MDI frame window, when the user moves the FrameBar or SheetBar

 

 

Back to top

 

 

 

WINDOW - CENTRE ON OPENING

 

Put this function in an ancestor window:

 

Function: wf_center()

  int iScreenHt, iScreenWid

  environment le_env

  // Get screen size from environment

  GetEnvironment( le_env )

  iScreenHt = PixelsToUnits( le_env.ScreenHeight, YPixelsToUnits! )

  iScreenWid = PixelsToUnits( le_env.ScreenWidth, XPixelsToUnits! )

  // Centre me!

this.Move( ( iScreenWid - this.Width ) / 2, ( iScreenHt - this.Height ) / 2 )

Back to top