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.
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.
A table doesnt 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.
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.
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.
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.
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
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
// Perform the AND
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
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:
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
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, "" )
// enable transaction registration (the registration service is need when using more than one database)
// register SQLCA (the connection to the first database which was connect to the usual way)
// 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
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)
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:
li_rc = this.GetChild("ddlbtest", ldwc_ddlb)
IF childid = Handle(ldwc_ddlb) THEN
CHOOSE CASE notificationcode
Post Event ue_DDLBRowFocusChanged()
Event ActionID from WordParm
RowFocusChanged 2048, 2049
Left Button Up 2313
Retrieve End 769
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:
where "calendar.bmp" may be a small calendar or a drop down arrow. Give the computed field a name;
2. Enable the DDCalendar service with the "NONE" option:
3. Add code to the DW clicked event to drop the Calendar:
IF dwo.name = "ship_date_arrow" THEN
IF IsValid(iuo_calendar) THEN
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.
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)
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)
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
pboui050.dll OLE 2 user interface
pbibm050.dll Database interface - IBM (optional)
pbnet050.dll Database interface - net gateway (optional)
And! -- PFCCOMM.DLL, PFCFLSRV.DLL
pbdwe60.dll datawindow engine
pbmss60.dll Microsoft direct driver - replace with
pbrtc60.dll run time library
pbtra60.dll database trace library (useful for live user
pbvm60.dll main virtual machine
PFCCOM32.DLL pfc printing library
PFCFLSRV.DLL pfc file services library
PBODB60.DLL ODBC connection
Use the FileExists() function, and look for the file named '.' in the desired directory. If the directory exists, it will
contain '.' and '..' as files.
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 )
lw_1.closeUserObject( llb_1 )
Close( lw_1 )
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:
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"
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.
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
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))
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))"
You cant. 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
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.
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.
FOR i = 1 TO Integer( dw_1.dwDescribe( "DataWindow.Column.Count)" )
sColNames[ I ] = <dwReference>.describe( "#" + string( i ) + ".name" )
Integer li_obj, li_max_objects
li_max_objects = UpperBound( windowname.Control )
FOR li_obj = 1 TO li_max_objects
CHOOSE CASE windowname.Control[ li_obj ].TypeOf()
l_dw = windowname.Control[ li_obj ]
ls_classname = l_dw.ClassName( )
CASE ELSE // other window object types
(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
IF This.GetRow() = This.RowCount() AND This.GetColumnName() = 'mylastone' THEN
ll_NewRow = This.InsertRow(0)
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
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
ll_row = this.GetRow()
ll_rowCount = this.RowCount()
if ll_rowCount <= 0 then
if ll_row > ll_rowCount then
ll_rowTarget = ll_rowCount
if ll_row = 0 then
ll_rowTarget = 1
li_rc = this.ScrollNextRow( )
li_rc = this.SetRow( ll_RowTarget )
// PB BUG
if this.GetRow() <> ll_rowTarget then this.fu_scrollToRow( ll_rowTarget )
this.fu_scrollToRow( ll_rowTarget )
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.
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.
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.
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.
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.
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 )
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"
$PBExportComments$Kernell32 API functions
global type nvo_kernell32 from nonvisualobject
// THIS MUST BE DONE IN CODE -- STRUCTURE PAINTER DOES NOT SUPPORT IT
type str_filetime from structure
type str_win32_find_data from structure
global type nvo_kernell32 from nonvisualobject autoinstantiate
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"
// MAX_PATH constant for file operations
Private constant long MAX_PATH = 260
public function integer of_dirtoarray (string path, ref string lista)
public function integer of_dirtoarray (string path, ref string lista);long
n = 0
lul_handle = FindFirstFile(path, str_find)
n += 1
lista[n] = str_find.filename
lb_fin = FindNextFile(lul_handle, str_find)
loop while lb_fin
TriggerEvent( this, "constructor" )
TriggerEvent( this, "destructor" )
integer i,n, li_fn
li_fn = FileOpen("c:\temp\dll.txt", LineMode!, Write!, LockWrite!, Replace!)
ls_lista = ''
n = inv_kernell.of_dirtoarray("c:\windows\system\*.dll", ls_lista)
for i=1 to n
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().
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
Subroutine GlobalMemoryStatus(Ref str_memorystatus astr_memorystatus) &
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 lpTotalNumberOf Bytes,
which is available only in Windows NT and Windows 95 (OSR 2). Documented in Windows SDK
// string f_dec_to_hex ( integer aiNumber), 0 <= n <= 15
// Converts integer from 0 - 15 into character hex representation
Choose Case aiNumber
sHexChar = 'A'
sHexChar = 'B'
sHexChar = 'C'
sHexChar = 'D'
sHexChar = 'E'
sHexChar = 'F'
sHexChar = String(aiNumber)
// 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 ) )
Return f_dec_to_hex( alNumber )
// string f_convert_char_to_hex( string asString )
// Converts an input string into a hex character string
string sResult, sHexChar
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
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.
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)
IF ii_bShowNotConnected THEN
This.AddItem ( ls_LocalName + "~t-not connected" )
FUNCTION UINT WNetGetConnectionA( ref string ln, ref string rn, ref uInt cb ) library "mpr.dll"
// mailSession nonvisual object signs on and establishes a MAPI session.
String ls_email_address, ls_attach_name
mailReturnCode. Returns one of the following values:
If any argument's value is NULL, mailLogon returns NULL.
// mailMessage obj is a system struct with info about a specific mail message. A mailMessage object has no events.
// mailFileDescription obj is a system struct with info about attachment file to a mail message. no events.
// 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
// 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.')
//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.name = 'Lastname, Firstname'
Open( w_email_address )
ls_email_address = Message.StringParm
mMsg.Recipient.name = ls_email_address
//mMsg.Recipient.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 = mAttach
// Send the mail
mRet = mSes.mailSend ( mMsg )
IF mRet <> mailReturnSuccess! THEN
MessageBox("Mail Send", 'Mail not sent')
// 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
//DESTROY the Mail session that was created with the CREATE statement.
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
Then, to use it...
lu_temp = message.powerobject // Cast it to type u_myObject
lu_myObject = create u_myObject
Finding the mouse position relative to the current window, the frame etc. Create a structure as follows and save as
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
lb_retval = GetCursorPos( lstr_mouseloc )
if lb_retval=true then
MessageBox("Cursor Position", "X = " + string(lstr_mouseloc.xposition) + " Y = "&
MessageBox("Cursor Position", "X = " + string(PixelsToUnits(PointerX(),XPixelsToUnits!)) + " Y = "&
MessageBox("Error","Error finding the mouse position.")
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.
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.
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 )
IF ii_bShowNotConnected THEN
This.AddItem ( ls_LocalName + "~t- not connected" )
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
inv_selectionAttrib.ia_returnVal[ll_counter] = dw_master.object.cust_id[ll_row]
inv_selectionAttrib.ia_returnVal[ll_counter] = dw_master.object.cust_name[ll_row]
ll_row = dw_master.getSelectedRow(ll_row)
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)
Add a button to the PowerBar as follows:
Right click on PowerBar.
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
You now have a convenient toolbar button to bring up PFC help
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
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 = ""
If IsNull(al_long) or al_long< 0 Then
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
// 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 = ""
There is a bug in of_getBit() that truncates negative and >32K values. Here is the fix.
Scope: public object function
Prototype: boolean of_getbit (long al_decimal,
// 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
If IsNull(al_decimal) or IsNull(ai_bit) then
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
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.
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.
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.
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.
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.
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.
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.
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")
Messagebox("Setting new printer name", "Failed")
//STEP 4: Get the new default printer's driver and port.
ls_printer = sle_new.text
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")
Messagebox("Warning", "An error has occurred")
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,
string sSystemName, sWindowsName, sDriverPort, sPrinter
sPrinter = sle_1.text
// 1. Get a list of
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")
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)
// Windows name, use as is
sWindowsName = sSystemName[iPrinter]
if sWindowsName =
sPrinter then exit
if iPrinter >
// Unable to find printer in list
MessageBox("Error", "Printer " + sPrinter + " could not be found.~r~n" + "Unable to set " + sPrinter + " as the default printer")
iReturn = RegistryGet("HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Devices", &
if iReturn <> 1 then
MessageBox("Error", "Unable to get printer details.~r~n" + "Unable to set " + sPrinter + " as the default printer")
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")
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
In the rbuttondown event use the following code.
// Prevent default popup menu
Note: return 0 will cause the internal rmb popup menu to be displayed. (Paul Blackmore)
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.
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 ;
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"
String ls_ipaddress, ls_hostname
Integer li_version, li_rc
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()
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
li_rc = WSAGetLastError()
|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.|
When user presses a key and the insertion point is not in RichTextEdit or DataWindow edit
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.|
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|
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|
Put this function in an ancestor window:
int iScreenHt, iScreenWid
// 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