pfc – Anvil of Time https://anvil-of-time.com Nature Forges Everything on the Anvil of Time Thu, 11 Mar 2021 16:25:49 +0000 en-US hourly 1 PowerBuilder – Resizing a Response Window or User Object https://anvil-of-time.com/powerbuilder/powerbuilder-resizing-a-response-window-or-user-object/ Wed, 23 May 2012 22:16:38 +0000 http://anvil-of-time.com/wordpress/?p=1324 Here is a technique you can use to resize a response window or userobject as needed. It makes use of the GetWindowLong and SetWindowLong Windows API methods. The oldest reference I found to this is from Eric Aling back in 2000. In a nutshell, you are changing the border around the object to one which Windows allows to be resized. What’s even nicer about this is the standard resize events are triggered so you can reposition/change objects within the window as well (PFC resize service for example). From Eric’s original post:

"The Get function retrieves the complete defenition of the window in 
a big long variable. All the bits in this long value describe the window. 
So there are bits for the type of border (which indicates if a window is 
resizable), menu, colors etc.etc. We can modify this long value, altering 
the design of the window. Using the SetWindowLong() we update the window with 
our specific modifications."

I used this in ancestor code of a userobject I use to create visible panels within the main window.

// Unicode declarations, used 'A' for ANSI
function long GetWindowLongW (long hWindow, integer nIndex) Library "user32.dll"
function long SetWindowLongW (long hWindow, integer nIndex, long dwNewLong) library "user32.dll"

// code in my post constructor event
n_cst_numerical lnv_num // PFC numeric service
long		ll_Styles
boolean		lb_Control

constant long	WS_THICKFRAME = 262144
constant long	WS_SYSMENU = 524288

ll_styles = GetWindowLongw(handle(THIS), -16)
if ll_styles <> 0 then

	ll_styles = lnv_num.of_BitWiseOr(ll_styles, WS_THICKFRAME)
// don't need control menu but leave this for reference 
//	if ab_Control then 
//		ll_styles = lnv_num.of_BitWiseOr(ll_styles, WS_SYSMENU)
//	end if

	SetWindowLongW(handle(THIS), -16, ll_styles)
end if

At first my UO cannot be resized.

Now it can!

I’ve seen other references to these API calls for manipulating windows for other things but I have not investigated further.

]]>
PowerBuilder – Type ahead style dropdown datawindow columns https://anvil-of-time.com/powerbuilder/powerbuilder-type-ahead-style-dropdown-datawindow-columns/ Tue, 07 Jun 2011 01:20:08 +0000 http://anvil-of-time.com/wordpress/?p=776 This type of functionality is one of those things which fills a gap so completely and perfectly that when you encounter some application (and almost all web pages) which doesn’t have it you groan to yourself. In the ancient past this was alternatively referred to as ‘Quicken style’ dropdowns since that application was one very common example in use. Basically this functionality is used on dropdown datawindows to allow the user to type in a series of characters which cause the control to filter the entries so that finding the desired one is quick and easy. The PFC has a service attached to the datawindow ancestor which does the same thing but here is the same functionality encapsulated into a single NVO.

Sample Application Screen Shots:

Image one shows the standard PB dropdown which is basically useless outside of using the mouse pointer.


Image two shows the Type Ahead interface where the highlighted row in the dropdown responds to user typing.

On the window containing the datawindow(s) you wish to have this functionality add the following:

//getfocus
// Use this method if you have several datawindows on a window which you wish to have this type of functionality.  If not you
// could just put this code in the window postopen type event.
datawindow ldw
ldw = THIS
nvo_grid.uf_set_current_dw(ldw)


//editchanged
// map nvo event to datawindow event
CHOOSE CASE dwo.name
	CASE 'pri_pro_int_id'
		nvo_grid.event ue_editchanged(row, dwo, data)
END CHOOSE

		
//itemfocuschanged
// map nvo event to datawindow event
nvo_grid.event ue_itemfocuschanged(row, dwo)

The NVO is set to autoinstantiate and contains the following code.

instance variables
// dddw search as you type functionality
datawindow idw
datawindowchild idwc[]
string is_datawindow[] // names of datawindow/column combinations registered for typeahead functionality
integer     ii_currentindex
boolean		ib_performsearch=False
string		is_textprev

Two events.

ue_editchanged(ref long al_row, ref dwobject adwo, ref string as_data);
// mapped to editchanged event on datawindow where dddw typeahead is desired
boolean		lb_matchfound=False
integer		li_searchtextlen
long		ll_findrow
long		ll_dddw_rowcount
string		ls_dddw_displaycol
string		ls_foundtext
string		ls_findexp
string		ls_displaydata_value
string		ls_searchtext

// Check requirements.
If IsNull(adwo) or Not IsValid(adwo) Then Return

// Confirm that the search capabilities are valid for this column.
if ib_performsearch=False or ii_currentindex <= 0 THEN return

// Get information on the column and text.
ls_searchtext = as_data
li_searchtextlen = Len (ls_searchtext)

// If the user performed a delete operation, do not perform the search.
// If the text entered is the same as the last search, do not perform another search.
If (li_searchtextlen < Len(is_textprev)) or &
	(Lower (ls_searchtext) = Lower (is_textprev)) Then
	// Store the previous text information.
	is_textprev = ''
	Return 
End If

// Store the previous text information.
is_textprev = ls_searchtext

// Build the find expression to search the dddw for the text 
// entered in the parent datawindow column.
ls_dddw_displaycol = adwo.dddw.displaycolumn
ls_findexp = "Lower (Left (" + ls_dddw_displaycol + ", " + &
	String (li_searchtextlen) + ")) = '" + Lower (ls_searchtext) + "'"

// Perform the Search on the dddw.
ll_dddw_rowcount = idwc[ii_currentindex].rowcount()
ll_findrow = idwc[ii_currentindex].Find (ls_findexp, 0, ll_dddw_rowcount + 1)

// Determine if a match was found on the dddw.
lb_matchfound = (ll_findrow > 0)

// Set the found text if found on the dddw.
if lb_matchfound then
	// Get the text found.
	ls_foundtext = idwc[ii_currentindex].GetItemString (ll_findrow, ls_dddw_displaycol)
End If								

// For either dddw or ddlb, check if a match was found.
If lb_matchfound Then
	// Set the text.
	idw.SetText (ls_foundtext)

	// Determine what to highlight or where to move the cursor..
	if li_searchtextlen = len(ls_foundtext) THEN
		// Move the cursor to the end
		idw.SelectText (Len (ls_foundtext)+1, 0)
	else
		// Hightlight the portion the user has not actually typed.
		idw.SelectText (li_searchtextlen + 1, Len (ls_foundtext))
	end if
end if



ue_itemfocuschanged(long al_row, ref dwobject adwo);
// set index if column is in dddw array

int		li_index
string 	ls_dwcolname

ib_performsearch = False
ii_currentindex = 0
is_textprev = ''

If IsNull(adwo) or Not IsValid(adwo) Then Return
If IsNull(al_row) or al_row <= 0 Then Return
If IsNull(idw) or Not IsValid(idw) Then Return
// Get column name. (in 'datawindow name|column name' format)
ls_dwcolname = idw.classname() + '|' + adwo.Name

// Check if column is in the search column array.
li_index = uf_find_registered_dddw_columns(ls_dwcolname)
If li_index <= 0 Then Return
// can perform search on the column
ib_performsearch = True

// Store the current index.
ii_currentindex = li_index
// Store the previous text information.
is_textprev = idw.GetText()

Three functions

public function integer uf_register_dddw_columns (ref datawindow adw, string as_colname);
// set up datawindowchild array
datawindowchild ldwc
integer li_rc
long	ll_ac
string ls_desc, ls_val

FOR ll_ac = 1 to Upperbound(is_datawindow)
	IF is_datawindow[ll_ac] = adw.classname() + '|' + as_colname THEN
		RETURN 1 // already registered
	END IF
NEXT

ll_ac = upperbound(is_datawindow) + 1

is_datawindow[ll_ac] = adw.classname() + '|' + as_colname
// need allowedit property set for this functionality to work
// this must be done prior to the getchild call
ls_desc = as_colname + '.dddw.allowedit'
ls_val = adw.describe(ls_desc)
IF ls_val <> 'yes' THEN
	ls_desc += '=yes'
	ls_val = adw.modify(ls_desc)
END IF

// Get a reference to the DropDownDatawindow.
li_rc = adw.GetChild(as_colname, ldwc)
IF li_rc<>1 THEN RETURN -1
idwc[ll_ac] = ldwc
RETURN 1



public function integer uf_find_registered_dddw_columns (string as_dwcolname);
// finds array position of given datawindow name/column name array
integer	li_count
integer	li_i

// Get the size of the array.
li_count = upperbound(is_datawindow)

// Check for an empty array.
if li_count <= 0 THEN return 0

// Find column name in array.
for li_i=1 TO li_count
	if is_datawindow[li_i] = as_dwcolname THEN
		return li_i
	end if
next
// Column name not found in array.
RETURN 0



public subroutine uf_set_current_dw (ref datawindow adw);
//called from getfocus event on datawindow	with dddw typeahead capability
idw = adw

A couple things of note here. Since the dddw column must be set to allow edit, you will need to have appropriate data checking in the itemchanged event to prevent incorrect entries should the user just type something in and leave the field. Also, if you have defined the datawindow to not allow edits, the NVO will modify the datawindow object to allow it. This means any previous getchild references may be broken once the datawindow is modified.

A sample application is attached (PB11.5.1) to demonstrate the functionality.

]]>
PowerBuilder – Development vs. Production Mode https://anvil-of-time.com/powerbuilder/powerbuilder-development-vs-production-mode/ Wed, 23 Feb 2011 01:20:13 +0000 http://anvil-of-time.com/wordpress/?p=715 You can set up a variety of helpful methods for use while developing an application which you don’t want users to get their hands on when in production. One example of this is my Window Information Service . To make this only available when running from the Powerbuilder IDE use the following steps.

Set up some sort of global application object. The PFC application object is a prime example of this. Here we will use ‘gnv_app’.

Declare a couple of instance variables on the object. Something like

>boolean ib_inPB
application iapp_object

In the open event of your main window you can then do:

 gnv_app.iapp_object = GetApplication()
gnv_app.ib_inPB = (handle(nv_app.iapp_object) = 0)

From Powerbuilder Help:
“As far as Windows is concerned, your application does not have a handle when it is run from PowerBuilder. When you build and run an executable version of your application, the Handle function returns a valid handle for the application.”

Now you can implement your development code inside an “IF gnv_app.ib_inpb THEN…” block.

]]>
PowerBuilder – PFC String service extension https://anvil-of-time.com/powerbuilder/powerbuilder-pfc-string-service-extension/ Tue, 22 Feb 2011 01:20:17 +0000 http://anvil-of-time.com/wordpress/?p=711 Here is a simple modification to the PFC string service (n_cst_string) which will parse out an array of any datatype and returned a delimited string of the contents. The new method is a polymorph of the existing of_arraytostring.

public function long of_arraytostring (any aa_array[], string as_delimiter, ref string as_string)

// convert array to string array and then send to standard PFC function
long ll_ac, ll_i
string ls_array[], ls_format

ll_ac = Upperbound(aa_array)
FOR ll_i = 1 to ll_ac
	IF ls_format = '' THEN
		CHOOSE CASE Classname(aa_array[ll_i])
			CASE "string","char"
			CASE "datetime"
				ls_format = 'mm-dd-yyyy hh:mm:ss'
			CASE "date"
				ls_format = 'mm-dd-yyyy'
			CASE "time"
				ls_format = 'hh:mm:ss'
			CASE ELSE
				
		END CHOOSE
	END IF
	ls_array[ll_i] = string(aa_array[ll_i], ls_format)
NEXT

RETURN of_arraytostring(ls_array,as_delimiter,as_string)
]]>
PowerBuilder – Next Key Service https://anvil-of-time.com/powerbuilder/powerbuilder-next-key-service/ Wed, 12 Jan 2011 01:20:18 +0000 http://anvil-of-time.com/wordpress/?p=667 As an extension of the next key stored procedure article, here is a pretty easy to implement a next surrogate key service to use with the PFC. If you don’t use the PFC it is still fairly easy to use with some modification. You can use this service anytime you want to get a sequential number, such as a document number or a sequence number. These services use a DataStore to call the spcmn_getNextKeyResultSet stored procedure, which returns the next key for the specified table.

Next Key Class (n_cst_nextKey)

This is the base class that adds “Next Key” functionality to a PFC based PowerBuilder application. This class can be called directly by the developer but is mainly used by the “Next Key” Application, DataWindow, and DataStore services.

Example usage

// reference to the next key object
n_cst_nextKey lnv_nextKey

// create the next key object
lnv_nextKey = create n_cst_nextKey

// set the next key's trans object; note: this step is not required as SQLCA is the default
lnv_nextKey.of_setTransObject (SQLCA)

// example: getting next key for "Table"
long ll_key
ll_key = lnv_nextKey.of_getNextKey ("Table")

// example: getting next 10 keys for "Table"
long ll_key
ll_key = lnv_nextKey.of_getNextKey ("Table", 10) 

// destroy the object
destroy (lnv_nextKey)

Next Key Application Service (n_cst_nextKey instance variable added to n_cst_appmanager)

This service adds the Next Key class to the PFC application manager class, n_cst_appmanager. When activated, this service adds next key functionality across an entire application. One benefit is that it is instantiated only once and persists the life of the application; therefore, there is very little overhead involved with using this service.

Example usage

In the constructor event of the application manager (typically a descendant of n_cst_appmanager).

// start the next key service
of_setNextKey (true)

After a connection to the database is established, call of_setTransObject( ) to set the trans object. This method need only be called for trans objects other than SQLCA since SQLCA is the default.

// set the next key's trans object; note: this step is not required as SQLCA is the default
gnv_app.inv_nextKey.of_setTransObject (ltr_object)

From here on out, the next key service can be called from anywhere in the application with a single line.

// example: getting next key for "Table"
long ll_nextKey
ll_nextKey = gnv_app.inv_nextKey.of_getNextKey ("Table")

// example: getting next 10 keys for "Table"
long ll_nextKey
ll_nextKey = gnv_app.inv_nextKey.of_getNextKey ("Table", 10)

Next Key DataWindow Service (n_cst_dwsrv_nextKey added to u_dw)

This service adds the Next Key class to the PFC DataWindow control class, u_dw. When activated, this service adds next key functionality for a specific DataWindow control. The main benefit with this service is that key columns in a dataobject can be registered to a table in the constructor event of a DataWindow control, saving the developer from writing the same code for each DataWindow in the application.

There are two ways to register columns with tables: automatic and manual.

Automatic registering allows the writing of generic objects. Registering is done through an assignment in the tag value of a column (e.g., “nextkey=Table”). With automatic registering, of_register( ) is called with no arguments.

Manual registering requires the calling of of_register( ) with explicit table names and column names. There is less overhead with this method because the Next Key class does not have to hunt through tag values columns and tables to register.

Example usage

In the constructor event of the DataWindow control.

// start the next key service
of_setNextKey(true)

// set the next key's trans object; note: this step is not required as SQLCA is the default
inv_nextKey.of_setTransObject (ltr_object)

Manual registering

// manually register columns to tables
inv_nextKey.of_register ("Table", "Column")
inv_nextKey.of_register ("Table2", "Column2")

Automatic registering

// automatically register columns to tables based on dataobject tag values
// "Column" has tag = 'nextkey=Table'
// "Column2" has tag = 'nextkey=Table2'
inv_nextKey.of_register( )

Some advanced examples of getting keys:

// get next key and apply to all rows in a DataWindow
dw_test.inv_nextKey.of_getNextKey("Table", "Column")

// get next key and apply to selected rows in a DataWindow based on an expression
dw_test.inv_nextKey.of_getNextKey("Table", "Column", "isNull(Column)")

// get next key and apply to all rows in a DataWindow, without remembering the row status
dw_test.inv_nextKey.of_getNextKey("Table", "Column", false)

// get next key and apply to selected rows in a DataWindow based on an expression, without remembering the row status
dw_test.inv_nextKey.of_getNextKey("Table", "Column", "isNull(Column)", false)

Next Key DataStore Service (n_cst_dssrv_nextKey instance variable added to n_ds)

This service adds the Next Key class to the PFC DataStore class, n_ds. When activated, this service adds next key functionality for a specific DataStore object. Due to the nature of this object, registering of tables and columns is not available.

Example usage

// start the next key service
ids_object.of_setNextKey (true)

// set the next key's trans object; note: this step is not required as SQLCA is the default
ids_object.inv_nextKey.of_setTransObject (ltr_object)

// get next key and apply to all rows in a DataStore
ids_test.inv_nextKey.of_getNextKey("Table", "Column")

// get next key and apply to selected rows in a DataStore based on an expression
ids_test.inv_nextKey.of_getNextKey("Table", "Column", "isNull(Column)")

// get next key and apply to all rows in a DataStore, without remembering the row status
ids_test.inv_nextKey.of_getNextKey("Table", "Column", false)

// get next key and apply to selected rows in a DataStore based on an expression, without remembering the row status
ids_test.inv_nextKey.of_getNextKey("Table", "Column", "isNull(Column)", false)

Export of n_cst_dwsrv_nextkey object for Datawindows

$PBExportHeader$n_cst_dwsrv_nextkey.sru
$PBExportComments$Next Key DataWindow Service
forward
global type n_cst_dwsrv_nextkey from n_cst_dwsrv
end type
end forward

global type n_cst_dwsrv_nextkey from n_cst_dwsrv
event type long ue_insertrow ( long al_row )
end type
global n_cst_dwsrv_nextkey n_cst_dwsrv_nextkey

type variables

protected:
n_cst_nextKeyAttrib inv_nextKeyTables
n_cst_nextKey inv_nextKey
n_tr itr_object
end variables

forward prototypes
public function integer of_settransobject (n_tr atr_object)
public function integer of_register (string as_tablename, string as_columnname)
public function integer of_getnextkey (string as_tablename, string as_columnname, string as_expression)
public function integer of_getnextkey (string as_tablename, string as_columnname, boolean ab_rememberrowstatus)
public function integer of_getnextkey (string as_tablename, string as_columnname, string as_expression, boolean ab_rememberrowstatus)
public function integer of_getnextkey (string as_tablename, string as_columnname)
public function integer of_register ()
end prototypes

event ue_insertrow;
// 
integer	li_i
integer	li_numKeys
long     ll_nextKey
string   ls_tableName, ls_columnName

dwItemStatus le_rowstatus

// validate the next key application service is in use
if not isValid(inv_nextKey) then return FAILURE

// Loop thru all the tables.
li_numKeys = upperBound (inv_nextKeyTables.is_tableName) 
For li_i = 1 to li_numKeys

	// get table and column names
	ls_tableName  = inv_nextKeyTables.is_tableName[li_i]
	ls_columnName = inv_nextKeyTables.is_columnName[li_i]

	// get the next key for the table
	ll_nextKey = inv_nextKey.of_getNextKey(ls_tableName)

	if ll_nextKey = 0 then return FAILURE

	// set the key
	if idw_requestor.setItem(al_row, ls_columnName, ll_nextKey) <> 1 then 
		return FAILURE
	end if

next 

// convert row back from NewModified! to New!.
le_rowstatus = idw_Requestor.GetItemStatus(al_row, 0, Primary!)
If le_rowstatus = NewModified! Then
	idw_requestor.setItemStatus(al_row, 0, Primary!, NotModified!)
end if

return al_row
end event

public function integer of_settransobject (n_tr atr_object);
// 
integer li_rc

// validate
if IsNull (atr_object) or not IsValid (atr_object) then return FAILURE

// set the trans object
li_rc = inv_nextKey.of_setTransObject (atr_object)

// remember the trans object
if li_rc = 1 then
	itr_object = atr_object
end if

return li_rc

end function

public function integer of_register (string as_tablename, string as_columnname);
// 
Integer	li_newUpper
String	ls_id

// Verify required reference.
If isNull(idw_requestor) Or Not isValid(idw_requestor) Then Return FAILURE

// Trim the arguments.
as_tableName = Trim(as_tableName)
as_columnName = Trim(as_columnName)

// Verify passed arguments.
If isNull(as_tableName) or len(as_tableName) = 0 Then Return FAILURE
If isNull(as_columnName) or len(as_columnName) = 0 Then Return FAILURE

// validate column is in requestor
ls_id = idw_requestor.describe (as_columnName + ".ID")
if not isNumber(ls_id) then return FAILURE

// Establish the boundaries of the array.
li_newUpper = UpperBound (inv_nextKeyTables.is_tableName) + 1

// Set the columns.
inv_nextKeyTables.is_tableName[li_newUpper] = as_tableName
inv_nextKeyTables.is_columnName[li_newUpper] = as_columnName

Return SUCCESS

end function

public function integer of_getnextkey (string as_tablename, string as_columnname, string as_expression);
// 
// pass on event with defaults
return this.of_getNextKey(as_tableName, as_columnName, as_expression, true)
end function

public function integer of_getnextkey (string as_tablename, string as_columnname, boolean ab_rememberrowstatus);
// 
// apply keys to a column for all rows in this datawindow
long ll_row, ll_rowCount, ll_numKeys, ll_nextKey
dwItemStatus le_rowStatus

// get total number of rows
ll_rowCount = idw_requestor.rowCount()
if ll_rowCount < 1 then return ll_rowCount

// number of keys = all rows	
ll_numKeys = ll_rowCount
	
// get block of keys
ll_nextKey = inv_nextKey.of_getNextKey(as_tableName, ll_numKeys)

// apply keys to all rows
ll_row = 1
do while ll_row > 0 and ll_row <= ll_rowCount

	// remember row status
	if ab_rememberRowStatus then
		le_rowStatus = idw_Requestor.getItemStatus(ll_row, 0, primary!)
	end if

	// set the column with the next key
	idw_requestor.setItem(ll_row, as_columnName, ll_nextKey)
	
	// reset row status to the remembered row status
	if ab_rememberRowStatus then
		
		// if the row status was originally new! or notModified!, then changing
		// it to notModified! will reset the row status. if the row status
		// was either newModified! or dataModified!, then the row should
		// already have its original row status.
		
		if le_rowStatus = new! or le_rowStatus = notModified! then
			idw_requestor.setItemStatus(ll_row, 0, primary!, NotModified!)
		end if
		
	end if

	ll_nextKey++
	ll_row++
	
loop

return 1

end function

public function integer of_getnextkey (string as_tablename, string as_columnname, string as_expression, boolean ab_rememberrowstatus);
// 
// apply keys to a column for selected rows in a datawindow based
// based on an expression
long ll_row, ll_rowCount, ll_numKeys, ll_nextKey
dwItemStatus le_rowStatus

// get total number of rows
ll_rowCount = idw_requestor.rowCount()
if ll_rowCount < 1 then return ll_rowCount

// count matching rows
ll_row = idw_requestor.find(as_expression, 1, ll_rowCount)
do while ll_row > 0 and ll_row <= ll_rowCount
	ll_numKeys++
	ll_row++
	ll_row = idw_requestor.find(as_expression, ll_row, ll_rowCount)
loop

// get out if no matches
if ll_numKeys < 1 then return ll_numKeys

// get block of keys
ll_nextKey = inv_nextKey.of_getNextKey(as_tableName, ll_numKeys)

// apply keys to the only matching rows
ll_row = idw_requestor.find(as_expression, 1, ll_rowCount)
do while ll_row > 0 and ll_row <= ll_rowCount

	// remember row status
	if ab_rememberRowStatus then
		le_rowStatus = idw_Requestor.getItemStatus(ll_row, 0, primary!)
	end if

	// set the column with the next key
	idw_requestor.setItem(ll_row, as_columnName, ll_nextKey)
	
	// reset row status to the remembered row status
	if ab_rememberRowStatus then
		
		// if the row status was originally new! or notModified!, then changing
		// it to notModified! will reset the row status. if the row status
		// was either newModified! or dataModified!, then the row should
		// already have its original row status.
		
		if le_rowStatus = new! or le_rowStatus = notModified! then
			idw_requestor.setItemStatus(ll_row, 0, primary!, NotModified!)
		end if
		
	end if

	ll_nextKey++
	ll_row++
	
	ll_row = idw_requestor.find(as_expression, ll_row, ll_rowCount)
	
loop
	
return 1

end function

public function integer of_getnextkey (string as_tablename, string as_columnname);
// 
// pass on call using defaults
return this.of_getNextKey(as_tableName, as_columnName, true)
end function

public function integer of_register ();
// 
// automatic registering of tables and columns with the next key service
// this method looks for objects on a dataobject with a tag of "nextkey=table"
n_cst_string lnv_string
string ls_objects[], ls_tag, ls_tableName, ls_columnName
integer li_objectCount, li_objectPointer

// make sure base datawindow service is started
if not isValid (idw_requestor.inv_base) then
	idw_requestor.of_setBase(true)
end if

// get dataobject objects into an array
li_objectCount = idw_requestor.inv_base.of_getObjects (ls_objects[])

// loop through objects
for li_objectPointer = 1 to li_objectCount
	
	// get object name
	ls_columnName = ls_objects[li_objectPointer]
	
	// get tag for object
	ls_tag = idw_requestor.describe(ls_columnName + ".tag")

	// get "nextkey" key.
	if not isNull(ls_tag) and len(trim(ls_tag)) > 0 then
		ls_tableName = lnv_string.of_getKeyValue (ls_tag, "nextkey", "|")
	end if
	
	// register table/column with the next key service
	if not isNull(ls_tableName) and len(trim(ls_tableName)) > 0 then
		this.of_register(ls_tableName, ls_columnName)
	end if
	
next

return 1
end function

on n_cst_dwsrv_nextkey.create
call super::create
end on

on n_cst_dwsrv_nextkey.destroy
call super::destroy
end on

event constructor;call super::constructor;
// 
inv_nextKey = create n_cst_nextKey
end event

event destructor;call super::destructor;
// 
if isValid(inv_nextKey) then destroy(inv_nextKey)
end event

Export of n_cst_dssrv_nextkey object for Datastores

$PBExportHeader$n_cst_dssrv_nextkey.sru
$PBExportComments$Next Key DataWindow Service
forward
global type n_cst_dssrv_nextkey from n_cst_dssrv
end type
end forward

global type n_cst_dssrv_nextkey from n_cst_dssrv
end type
global n_cst_dssrv_nextkey n_cst_dssrv_nextkey

type variables

protected:
n_cst_nextKey inv_nextKey
n_tr itr_object
end variables

forward prototypes
public function integer of_settransobject (n_tr atr_object)
public function integer of_getnextkey (string as_tablename, string as_columnname)
public function integer of_getnextkey (string as_tablename, string as_columnname, string as_expression)
public function integer of_getnextkey (string as_tablename, string as_columnname, string as_expression, boolean ab_resetrowstatus)
public function integer of_getnextkey (string as_tablename, string as_columnname, boolean ab_resetrowstatus)
end prototypes

public function integer of_settransobject (n_tr atr_object);
// 
integer li_rc

// validate
if IsNull (atr_object) or not IsValid (atr_object) then return FAILURE

// set the trans object
li_rc = inv_nextKey.of_setTransObject (atr_object)

// remember the trans object
if li_rc = 1 then
	itr_object = atr_object
end if

return li_rc


end function

public function integer of_getnextkey (string as_tablename, string as_columnname);
// 
// pass on call with defaults
return this.of_getNextKey(as_tableName, as_columnName, true)

end function

public function integer of_getnextkey (string as_tablename, string as_columnname, string as_expression);
// 
// pass on call with defaults
return this.of_getNextKey(as_tableName, as_columnName, as_expression, true)
end function

public function integer of_getnextkey (string as_tablename, string as_columnname, string as_expression, boolean ab_resetrowstatus);
// 
// apply keys to a column for selected rows in a datawindow based
// based on an expression
long ll_row, ll_rowCount, ll_numKeys, ll_nextKey
dwItemStatus le_rowStatus

// get total number of rows
ll_rowCount = ids_requestor.rowCount()
if ll_rowCount < 1 then return ll_rowCount

// count matching rows
ll_row = ids_requestor.find(as_expression, 1, ll_rowCount)
do while ll_row > 0 and ll_row <= ll_rowCount
	ll_numKeys++
	ll_row++
	ll_row = ids_requestor.find(as_expression, ll_row, ll_rowCount)
loop

// get out if no matches
if ll_numKeys < 1 then return ll_numKeys

// get block of keys
ll_nextKey = inv_nextKey.of_getNextKey(as_tableName, ll_numKeys)

// apply keys to the only matching rows
ll_row = ids_requestor.find(as_expression, 1, ll_rowCount)
do while ll_row > 0 and ll_row <= ll_rowCount

	// remember row status
	if ab_resetRowStatus then
		le_rowStatus = ids_Requestor.getItemStatus(ll_row, 0, primary!)
	end if

	// set the column with the next key
	ids_requestor.setItem(ll_row, as_columnName, ll_nextKey)
	
	// reset row status
	if ab_resetRowStatus then
		
		// if the row status was originally new! or notModified!, then changing
		// it to notModified! will reset the row status. if the row status
		// was either newModified! or dataModified!, then the row should
		// already have its original row status.
		
		if le_rowStatus = new! or le_rowStatus = notModified! then
			ids_requestor.setItemStatus(ll_row, 0, primary!, NotModified!)
		end if
		
	end if

	ll_nextKey++
	ll_row++
	
	ll_row = ids_requestor.find(as_expression, ll_row, ll_rowCount)
	
loop
	
return 1

end function

public function integer of_getnextkey (string as_tablename, string as_columnname, boolean ab_resetrowstatus);
// 
// apply keys to a column for all rows in this datawindow
long ll_row, ll_rowCount, ll_numKeys, ll_nextKey
dwItemStatus le_rowStatus

// get total number of rows
ll_rowCount = ids_requestor.rowCount()
if ll_rowCount < 1 then return ll_rowCount

// number of keys = all rows	
ll_numKeys = ll_rowCount
	
// get block of keys
ll_nextKey = inv_nextKey.of_getNextKey(as_tableName, ll_numKeys)

// apply keys to all rows
ll_row = 1
do while ll_row > 0 and ll_row <= ll_rowCount
	
	// remember row status
	if ab_resetRowStatus then
		le_rowStatus = ids_Requestor.getItemStatus(ll_row, 0, primary!)
	end if

	// set the column with the next key
	ids_requestor.setItem(ll_row, as_columnName, ll_nextKey)
	
	// reset row status
	if ab_resetRowStatus then
		
		// if the row status was originally new! or notModified!, then changing
		// it to notModified! will reset the row status. if the row status
		// was either newModified! or dataModified!, then the row should
		// already have its original row status.
		
		if le_rowStatus = new! or le_rowStatus = notModified! then
			ids_requestor.setItemStatus(ll_row, 0, primary!, NotModified!)
		end if
		
	end if
	
	ll_nextKey++
	ll_row++
	
loop

return 1

end function

event constructor;call super::constructor;
// 
inv_nextKey = create n_cst_nextKey
end event

on n_cst_dssrv_nextkey.create
call super::create
end on

on n_cst_dssrv_nextkey.destroy
call super::destroy
end on

event destructor;call super::destructor;
// 
if isValid(inv_nextKey) then destroy(inv_nextKey)
end event

Datawindow Object Database Information from export

table(column=(type=decimal(0) updatewhereclause=yes name=nextkey dbname="nextKey" )
 procedure="1 execute dbo.spcmn_getNextKeyResultSet;1 @isTableName = :isTableName, @iiKeys = :iiKeys" arguments=(("isTableName", string),("iiKeys", number)) )

Once you have the stored procedures in your database, create a datawindow object with the spcmn_getNextKeyResultSet procedure as its datasource.

Stored Procedure for the NextKey datawindow object

This calls the spcmn_getNextKey procedure detailed in the earlier post.

/****** Object: Procedure [dbo].[spcmn_getNextKeyResultSet]   Script Date: 12/30/2010 10:17:41 AM ******/
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[spcmn_getNextKeyResultSet]') AND type in (N'P', N'PC'))
BEGIN
DROP PROCEDURE [dbo].[spcmn_getNextKeyResultSet];
END
GO

SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER OFF;
GO

CREATE PROCEDURE [dbo].[spcmn_getNextKeyResultSet]
    @isTableName varchar(128)  = null -- table to get next key for
   ,@iiKeys      decimal(18,0) = null -- number of keys to get; null = 1
AS
   set nocount on
   -- ----------------------------------------------------------
   -- Procedure: spcmn_getNextKeyResultSet
   -- Get next technical key(s) for the table name passed in.
   -- ----------------------------------------------------------

   declare @lsERRORTEXT varchar(255)
          ,@liERROR     int
          ,@liROWCOUNT  int
          ,@liTRANCOUNT int

   declare @liNextKey decimal(18,0)

   -- work around for the PowerBuilder IDE
   if @iiKeys = 0
   begin
      set @liNextKey = 0
      select @liNextKey 'nextKey'   
      return
   end
   -- initialize error handling
   select @liERROR = @@ERROR
   if @liERROR <> 0 goto ErrorHandler

   -- get next key
   exec spcmn_getNextKey @isTableName, @liNextKey output, @iiKeys

   select @liERROR = @@ERROR
   IF @liERROR <> 0 goto ErrorHandler

   select @liNextKey 'nextKey'

   return

ErrorHandler:
   if @@TRANCOUNT > @liTRANCOUNT --0
      rollback transaction

   select @lsERRORTEXT = isNull(@lsERRORTEXT, 'spcmn_getNextKeyResultSet: Unexpected Error Occurred!')
   raiserror(@lsERRORTEXT, 0, 1) WITH SETERROR

GO
GRANT EXECUTE ON [dbo].[spcmn_getNextKeyResultSet] TO [public];
GO

Datastore (n_ds) object export

$PBExportHeader$n_ds.sru
$PBExportComments$Extension Datastore class
forward
global type n_ds from pfc_n_ds
end type
end forward

global type n_ds from pfc_n_ds
end type
global n_ds n_ds

type variables

public:
n_cst_dssrv_nextKey inv_nextKey
end variables

forward prototypes
public function integer of_setnextkey (boolean ab_switch)
end prototypes

public function integer of_setnextkey (boolean ab_switch);
//Check arguments
If IsNull(ab_switch) Then
	Return -1
End If

IF ab_Switch THEN
	IF IsNull(inv_nextKey) Or Not IsValid (inv_nextKey) THEN
		inv_nextKey = Create n_cst_dssrv_nextKey
		inv_nextKey.of_setRequestor ( this )
		Return 1
	END IF
ELSE 
	IF IsValid (inv_nextKey) THEN
		Destroy inv_nextKey
		Return 1
	END IF	
END IF

Return 0
end function

on n_ds.create
call super::create
end on

on n_ds.destroy
call super::destroy
end on

Datawindow (u_dw) object export

Nextkey service used in pfc_addrow and pfc_insertrow events

$PBExportHeader$u_dw.sru
$PBExportComments$Extension DataWindow class
forward
global type u_dw from pfc_u_dw
end type
end forward

global type u_dw from pfc_u_dw
end type
global u_dw u_dw

type variables

public:
n_cst_dwsrv_nextKey inv_nextKey
end variables

forward prototypes
public function integer of_setnextkey (boolean ab_switch)
end prototypes

public function integer of_setnextkey (boolean ab_switch);
if IsNull(ab_switch) then return FAILURE

if ab_Switch then
	if IsNull(inv_nextKey) or not IsValid (inv_nextKey) then
		inv_nextKey = Create n_cst_dwsrv_nextKey
		inv_nextKey.of_SetRequestor (this)
		return SUCCESS
	end if
else 
	if IsValid (inv_nextKey) then
		Destroy inv_nextKey
		return SUCCESS
	end if	
end if

return NO_ACTION
end function


on u_dw.create
call super::create
end on

on u_dw.destroy
call super::destroy
end on

event destructor;call super::destructor;
of_setNextKey(false)
end event

event pfc_addrow;// override of pfc event!

//////////////////////////////////////////////////////////////////////////////
//	Event:			pfc_addrow
//	Arguments:		None
//	Returns:			long - number of the new row that was inserted
//	 					0 = No row was added.
//						-1 = error
//	Description:	Adds a new row to the end of the DW
//////////////////////////////////////////////////////////////////////////////
long	ll_rc
boolean lb_disablelinkage

// Allow for pre functionality.
if this.Event pfc_preinsertrow() <= 0 then return NO_ACTION

// Is Querymode enabled?
if IsValid(inv_QueryMode) then lb_disablelinkage = inv_QueryMode.of_GetEnabled()

if not lb_disablelinkage then
	// Notify that a new row is about to be added.
	if IsValid ( inv_Linkage ) then inv_Linkage.Event pfc_InsertRow (0) 
end if

// Insert row.
if IsValid (inv_RowManager) then
	ll_rc = inv_RowManager.event pfc_addrow ()
else
	ll_rc = this.InsertRow (0) 
end if

// pass on event to the next key service
// added here so linkage service can know about keys
if isValid (inv_nextKey) then	inv_nextKey.event ue_insertRow (ll_rc) 

if not lb_disablelinkage then
	// Notify that a new row has been added.
	if IsValid ( inv_Linkage ) then inv_Linkage.Event pfc_InsertRow (ll_rc) 
end if

// Allow for post functionality.
this.Post Event pfc_postinsertrow(ll_rc)

return ll_rc
end event

event pfc_insertrow;// -- override of pfc event

//////////////////////////////////////////////////////////////////////////////
//	Event:			pfc_insertrow
//	Arguments:		None
//	Returns:			long - number of the new row that was inserted
//	 					0 = No row was added.
//						-1 = error
//	Description:	Inserts a new row into the DataWindow before the current row
//////////////////////////////////////////////////////////////////////////////
long	ll_currow
long	ll_rc
boolean lb_disablelinkage

// Allow for pre functionality.
if this.Event pfc_preinsertrow() <= PREVENT_ACTION then return NO_ACTION

// Get current row
ll_currow = this.GetRow()
if ll_currow < 0 then ll_currow = 0

// Is Querymode enabled?
if IsValid(inv_QueryMode) then lb_disablelinkage = inv_QueryMode.of_GetEnabled()
		
if not lb_disablelinkage then		
	// Notify that a new row is about to be added.
	if IsValid ( inv_Linkage ) then inv_Linkage.Event pfc_InsertRow (0) 
end if

// Insert row.
if IsValid (inv_RowManager) then
	ll_rc = inv_RowManager.event pfc_insertrow (ll_currow)
else
	ll_rc = this.InsertRow (ll_currow) 
end if

// pass on event to the next key service
// added here so linkage service can know about keys
if isValid (inv_nextKey) then	inv_nextKey.event ue_insertRow (ll_rc) 

if not lb_disablelinkage then		
	// Notify that a new row has been added.
	if IsValid ( inv_Linkage ) then inv_Linkage.Event pfc_InsertRow (ll_rc) 
end if
// Allow for post functionality.
this.Post Event pfc_postinsertrow(ll_rc)

return ll_rc
end event

Special thanks to A.J. Schroeder.

]]>
PowerBuilder – Item counter for datawindow entries https://anvil-of-time.com/powerbuilder/powerbuilder-item-counter-for-datawindow-entries/ Thu, 21 Oct 2010 01:20:34 +0000 http://anvil-of-time.com/wordpress/?p=461 This is a PFC based component I build for an application used to create purchasing requisitions. The main datawindow was used to enter new ‘documents’ and was too large to allow for more than one row to be visible at a time. Rather than use a common ‘vcr’ type control I created one which shows the current row number, the total number of rows, and the ability to go to any row within the total.

The component, u_itemcounter, is inherited from u_dw.

The dataobject, d_itemcounter, has an external datasource with three columns:

table(column=(type=number updatewhereclause=yes name=itmcount dbname="itmcount" initial="0" )
 column=(type=char(2) updatewhereclause=yes name=of dbname="of" initial="of" )
 column=(type=number updatewhereclause=yes name=itmtotal dbname="itmtotal" initial="0" )
 )

Example control

Instance variables:

u_dw	idw_source
long	il_currentrow
long	il_rowcount

The code in the control is as follows:

public function integer of_setsource (ref u_dw adw_source);
// sets the source datawindow
integer li_return
li_return = 0
idw_source = adw_source
RETURN li_return
end function

public subroutine of_setmaxrow (long al_row);
il_rowcount = al_row
this.setitem(1,'itmtotal',al_row)
end subroutine

public function long of_getcurrentrow ();
RETURN il_currentrow
end function

public subroutine of_setcurrentrow (long al_row);
il_currentrow = al_row
this.setitem(1,'itmcount',al_row)
end subroutine

event constructor;call super::constructor;
this.of_setbase(TRUE)
this.of_SetRowSelect(FALSE)
this.of_SetRowManager(FALSE)
this.of_SetSort(FALSE)
this.setrowfocusindicator(Off!)
end event

event losefocus;call super::losefocus;
long ll_data
IF this.rowcount( ) < 1 THEN RETURN 
ll_data = this.getitemnumber(1,'itmcount') 
IF ll_data > il_rowcount THEN
	ll_data = il_rowcount
END IF
of_setcurrentrow (ll_data)
idw_source.setrow(il_currentrow)
idw_source.scrolltorow(il_currentrow)
end event

event itemchanged;call super::itemchanged;
IF long(data) > il_rowcount THEN
	// ERROR CONDITION
	RETURN 1
ELSE
	of_setcurrentrow (long(data))
	idw_source.setrow(row)
	idw_source.scrolltorow(row)
END IF
end event

event type long pfc_insertrow();
call super::pfc_insertrow;
long	ll_maxrow
ll_maxrow = idw_source.rowcount()
of_setmaxrow(ll_maxrow)
of_setcurrentrow(ANCESTORRETURNVALUE)
RETURN ANCESTORRETURNVALUE
end event

To implement the control, place it on your window in a suitable location near the ‘master’ datawindow. In my case just above the master datawindow on the left edge. I named the control ‘dw_counter.’

When window opens, master control has three records

In the constructor event of the master datawindow add the following:

u_dw ldw
ldw = THIS
dw_counter.of_setsource(ldw) // set the link to the counter object

In the pfc_addrow method add the following:

long ll_rc
ll_rc = this.rowcount()
IF (dw_counter.rowcount() < 1) THEN
	dw_counter.event trigger pfc_insertrow()
ELSE
	dw_counter.of_setmaxrow(ll_rc)
END IF
RETURN ANCESTORRETURNVALUE

In the pfc_predeleterow method the following:

long ll_row
// reset the counter dw
IF ANCESTORRETURNVALUE = CONTINUE_ACTION THEN
	ll_row = dw_counter.of_getcurrentrow()
	IF (ll_row = this.Rowcount()) THEN
		dw_counter.of_setcurrentrow(ll_row - 1)
		dw_counter.of_setmaxrow(ll_row - 1)
	ELSE
		dw_counter.of_setmaxrow(this.Rowcount() - 1 )
	END IF
END IF
RETURN ANCESTORRETURNVALUE

And finally in the scrollvertical event the following:

long ll_row
string ls_row
// set counter to current row when scrolling
ls_row = this.Object.Datawindow.FirstRowOnPage
ll_row = Long(ls_row)
dw_counter.of_setcurrentrow(ll_row)

The master datawindow in the example window contains three records. To set the counter properly when the window opens the following is put in the pfc_postopen event:

dw_counter.event pfc_insertrow() // makes the control visible

Just after adding a record.

Moved to record two in the master datawindow


If you choose to create your component without the PFC you will obviously have to change the code.

Download example window (PFC based) here:  itemcounter example pbl

]]>
Powerbuilder – PFC Resize Service Extension / Max Height & Width https://anvil-of-time.com/powerbuilder/powerbuilder-resize-service-extension-max-height-width/ https://anvil-of-time.com/powerbuilder/powerbuilder-resize-service-extension-max-height-width/#respond Thu, 30 Sep 2010 01:25:45 +0000 http://anvil-of-time.com/wordpress/?p=408 The PFC window resize service is one of my favorite things in the entire class library as it can easily be implemented and adds a great bit of functionality to an application. One drawback, however, is there is no built in capability to limit the resizing of the objects on a window; either they resize and/or move or they don’t. This simple extension adds the capability to set maximum width and height values to the controls being resized so you don’t end up with an ugly display of a large blank space after the end of the columns inside a datawindow control.

To implement this extension you need to change the code in the n_cst_resizeattrib and n_cst_resize objects. Whether you do this at the PFC layer or some corporate layer is up to you.

In the n_cst_resizeattrib object add the following instance variables:

real		r_maxwidth = 0
real		r_maxheight = 0

In the n_cst_resize object do the following:

Alter the of_register function to return the array position ‘li_slot_available’.

Add the following functions:

public function integer of_setmaxcontrolwidth (integer ai_arraypos, real ar_width);
// 11.5.1 set maximum control width
// parms ai_arraypos - array position in the inv_registered control array
// 			ar_width - maximum witdth control can be resized to
//set r_maxwidth in n_cst_resizeattrib for specified array position
IF Upperbound(inv_registered) >= ai_arraypos THEN
	inv_registered[ai_arraypos].r_maxwidth = ar_width
	RETURN Success
ELSE
	RETURN Failure
END IF
end function

public function integer of_setmaxcontrolheight (integer ai_arraypos, real ar_height);
// 11.5.1 set maximum control width
// parms ai_arraypos - array position in the inv_registered control array
// 			ar_width - maximum height control can be resized to
//set r_maxheight in n_cst_resizeattrib for specified array position
IF Upperbound(inv_registered) >= ai_arraypos THEN
	inv_registered[ai_arraypos].r_maxheight = ar_height
	RETURN Success
ELSE
	RETURN Failure
END IF
end function

Then in the of_resize method modify as follows:

BEFORE:
//Save the 'exact' Width and Height attributes.
inv_registered[li_cnt].r_width = inv_registered[li_cnt].r_width + lr_resize_deltawidth	
inv_registered[li_cnt].r_height = inv_registered[li_cnt].r_height + lr_resize_deltaheight

AFTER:
// look at maximum values
IF (inv_registered[li_cnt].r_maxwidth > 0) AND (inv_registered[li_cnt].r_width + lr_resize_deltawidth > inv_registered[li_cnt].r_maxwidth) THEN
	// exceeds max width so set to max
	inv_registered[li_cnt].r_width = inv_registered[li_cnt].r_maxwidth
ELSEIF  (inv_registered[li_cnt].r_width =  inv_registered[li_cnt].r_maxwidth) AND  (inv_registered[li_cnt].r_maxwidth > 0) THEN
	IF inv_registered[li_cnt].b_scalewidth  AND (inv_registered[li_cnt].r_width > ai_newwidth) THEN
		// resize down from max width to fit on window (115 is width of vertical scroll bar)
		inv_registered[li_cnt].r_width = inv_registered[li_cnt].r_maxwidth +  (ai_newwidth - inv_registered[li_cnt].r_width ) - 115
	END IF
ELSE
	//Save the 'exact' Width and Height attributes.
	inv_registered[li_cnt].r_width = inv_registered[li_cnt].r_width + lr_resize_deltawidth	
END IF
			
IF (inv_registered[li_cnt].r_maxheight > 0) AND (inv_registered[li_cnt].r_height + lr_resize_deltaheight > inv_registered[li_cnt].r_maxheight) THEN
	// exceeds max so set to max
	inv_registered[li_cnt].r_height = inv_registered[li_cnt].r_maxheight
ELSEIF  (inv_registered[li_cnt].r_height =  inv_registered[li_cnt].r_maxheight) AND (inv_registered[li_cnt].r_maxheight > 0) THEN
		// resize down from max
	IF inv_registered[li_cnt].b_scaleheight  AND (inv_registered[li_cnt].r_height > ai_newheight) THEN
		// resize down from max height to fit on window (115 is width of horizontal scroll bar)
		inv_registered[li_cnt].r_height = inv_registered[li_cnt].r_maxheight +  (ai_newheight - inv_registered[li_cnt].r_height ) - 115
	END IF
ELSE
	inv_registered[li_cnt].r_height = inv_registered[li_cnt].r_height + lr_resize_deltaheight	
END IF

Now in the event you register the controls on the window do the following:

li_arraypos = this.inv_resize.of_Register(dw_1, this.inv_resize.SCALERIGHTBOTTOM)
IF li_arraypos > 0 THEN
	// set max width and height for dw_1
	this.inv_resize.of_setmaxcontrolwidth(li_arraypos, 6400)
	this.inv_resize.of_setmaxcontrolheight(li_arraypos, 3000)
END IF

NOTE: as of 10/1/2010 I am unable to get the height maximum piece to properly resize down from a maximum setting. This seems to be a bug but I will work on it some more as time permits. At this time I recommend not setting a maximum height for datawindows.

]]>
https://anvil-of-time.com/powerbuilder/powerbuilder-resize-service-extension-max-height-width/feed/ 0
PowerBuilder – PFC file, resize, and string services https://anvil-of-time.com/powerbuilder/powerbuilder-pfc-file-resize-and-string-services/ Wed, 29 Sep 2010 01:20:24 +0000 http://anvil-of-time.com/wordpress/?p=399 Although the PFC provide a wide range of capabilities, it can be a very tedious task to ‘strip out’ a specific piece of functionality for use in an ‘non PFC’ application. Here are the objects and modifications you need to do to strip out the File Service, Window Resize Service, and the String Service.

PFC.PBL
d_dirattrib
f_setfilesrv
s_pagesetupattrib
s_paperattrib
pfc_n_base
pfc_n_cst_baseattrib
pfc_n_cst_diratrrib
pfc_n_cst_dssrv
pfc_n_cst_dwobjectattrib
pfc_n_cst_filesrv
pfc_n_cst_filesrvaix
pfc_n_cst_filesrvhpux
pfc_n_cst_filesrvsol2
pfc_n_cst_filesrvunicode
pfc_n_cst_filesrvwin32
pfc_n_cst_infoattrib
pfc_n_cst_numerical
pfc_n_cst_resize
pfc_n_cst_resizeattrib
pfc_n_cst_string
pfc_n_ds
Modifications to pfc_n_ds:
Comment out the following:
//n_cst_dssrv_multitable	inv_multitable
//n_cst_dssrv_report		inv_report
//n_cst_dssrv_printpreview	inv_printpreview
//n_tr	itr_object
//public function integer of_settransobject (n_tr atr_object)
Alter the following methods:
pfc_printpreview, pfc_ruler, pfc_zoom, pfc_pagesetup, pfc_pagesetupdlg, pfc_accepttext, 
pfc_update, of_checkrequired, of_setmultitable, of_setreport, of_setprintpreview, sqlpreview, 
dberror, retrieveend  - comment out except for return values
of_getparentwindow, of_getparent - comment out debug service stuff
PFE.PBL
n_base
n_cst_baseattrib
n_cst_diratrrib
n_cst_dssrv
n_cst_dwobjectattrib
n_cst_filesrv
n_cst_filesrvaix
n_cst_filesrvhpux
n_cst_filesrvsol2
n_cst_filesrvunicode
n_cst_filesrvwin32
n_cst_infoattrib
n_cst_numerical
n_cst_resize
n_cst_resizeattrib
n_cst_string
n_ds

The way I did this was to copy the objects from the source PFC pbls into a set of application pbls (PFE.PBL and PFC.PBL) and then edit the source to make modifications where needed.

]]>
PowerBuilder ‘Gotcha’ – pfc Resize service and Tabpages https://anvil-of-time.com/powerbuilder/powerbuilder-gotcha-pfc-resize-service/ Thu, 09 Sep 2010 01:20:30 +0000 http://anvil-of-time.com/wordpress/?p=343 The window resize service is one of the best features of the PFC. With a few lines of code you can easily make your application much more resolution independent and user friendly. Something else which makes this service pretty cool is that it can be easily un-copoled from the rest of the pfc. All you need is n_base, n_cst_resize, and n_cst_resizeattrib which you can make by exporting the corresponding ‘pfc_’ objects, renaming them in the export file, and then importing them. You have to handle instantiating and destroying the object but that’s pretty minor.

Anyway I’m working on an existing multi tab window to which I want to add resize functionality. Using standard convention I use a ‘registration’ event posted from the window’s ‘Open’ event to create the service, set various parameters, and register the objects with the service. In the resize event of the window I trigger the pfc_resize event on the service but put some limiting logic if the window’s width is too large (my intention is to show all the columns in various datawindows but I don’t want a large blank area to the right of the datawindow control if the user is on a large monitor).

Everything seems fine in my testing so the code is checked in. Fast forward a couple of months and a co-worker is regression testing my code and I notice that sometimes a white rectangle is appearing between the bottom of the window and tabpage. I check the objects on the window, check the background colors, try tweeking the dimensions, etc. without luck. A google search brought me to this page on pfcguide.com. (As of 9-12-2011 this link is broken.) (Updated March 2021 to link to old PB9 PFC guide but not page I originally found.)

Notice this section:

In your Tab's Constructor event:

// Nothing is needed here...
// Because the tab pages are automatically resized when the tab control resizes

// Do not register the tabpages themselves or you will notice slight resize problems
// with your tabpages, since they are then being resized twice.

When I removed the registration of the tab pages themselves (not the objects on the tabs) – no more white rectangles.

]]>
PowerBuilder – Database transactions using PFC n_tr https://anvil-of-time.com/powerbuilder/powerbuilder-database-transactions-using-pfc-n_tr/ Fri, 20 Aug 2010 01:19:23 +0000 http://anvil-of-time.com/wordpress/?p=294 Here is a relatively painless way to handle transaction processing in your Powerbuilder application. This code assumes the use of the PFC but could be adapted without too much trouble.

This will work with the following database connections:

ADO.NET
ASE, SYC and SYJ Sybase Adaptive Server Enterprise
DIR Sybase DirectConnect
I10 Informix
IN9 Informix
JDB JDBC
ODBC (if driver and back-end DBMS support this feature)
OLE DB
SNC SQL Native Client for Microsoft SQL Server

The process involves the AutoCommit database preference. The Powerbuilder default is to set this to False which means that SQL statements are issued inside a transaction. Powerbuilder issues a BEGIN TRANSACTION statement at the start of a connection and issues another BEGIN TRANSACTION statement after each COMMIT or ROLLBACK statement. In our case the AutoCommit is set to True so we control the transactions.

In the n_tr object (or whatever your pfc based transaction object is called) make the following changes:

public function long of_begin ();

/* Issues a begin transaction by changing the autocommit flag from true to false. 
The assumption is that the initial connection is made with
the autocommit flag set to true. Changing autocommit from true to false
forces the start of a transaction.
*/
long	ll_rc = -10
this.SQLCode = 0
this.SQLErrtext = ''
if of_IsConnected() then
	this.autoCommit = false
	ll_rc = this.SQLCode
end if
return ll_rc

public function long of_end

/* The initial connection is made with autocommit = true. The of_start()
method changes autocommit from true to false, which begins the 
transaction. Calling of_end() changes the autocommit from false back
to true, which commits the transaction if it had not previously been
rolled back by a call to of_rollback(). This method is called by
both of_commit() and of_rollback and need not be called directly.
*/

long	ll_rc = -10

if of_IsConnected() then
	this.autoCommit = true
	ll_rc = this.SQLCode
end if

return ll_rc

public function long of_commit

/* Copied method from ancestor and changed "commit using this" to
a call to of_end(). The initial connection is made with autocommit
set to true. Calling of_begin() changes autocommit to false, which issues
a begin transaction statement. When of_commit() is called, the
transaction is commited by of_end() by chaning autocommit back to true.
*/

long	ll_rc = -10
string	ls_name

if of_IsConnected() then
	// If SQLSpy service is on, add to the history
	if IsValid (gnv_app) then
		if IsValid (gnv_app.inv_debug) then
			if IsValid (gnv_app.inv_debug.inv_sqlspy) then
				ls_name = this.is_Name
				if Len (ls_name) = 0 then
					ls_name = this.ClassName()
				end if
				gnv_app.inv_debug.inv_sqlspy.of_SQLSyntax ("Commit using " + ls_name )
			end if 
		end if
	end if

	//	commit using this;
	//	ll_rc = this.SQLCode
	ll_rc = this.of_end()
end if

return ll_rc

public function long of_rollback

/* Rollback the transaction the pfc way, then call of_end() to reset
the autocommit flag back to true. (Note: This forces an un-needed
begin tran and commit.)
*/

long ll_return

// call ancestor method
ll_return = super::of_rollback()

// End the transaction.
this.of_end()

return ll_return

Within the application code you then use the following code when you want to save data to the database. Naturally you need to use your transaction name if you created one other than the global transaction object SQLCA

Prior to update(s) being issued.

//begin transaction
IF (sqlca.of_begin( ) < 0) THEN
    messagebox('Database Error','Error starting transaction ~n~r' + sqlca.sqlerrtext)
    // return logic here
END IF

After datawindow updates or imbedded sql calls.

// update/insert/delete check
IF sqlca.sqlcode < 0 THEN
    ls_msg = sqlca.sqlerrtext
    sqlca.of_rollback()
    messagebox('Database Error','Error inserting into table -> partMaster. ~n~r' + ls_msg)
    //return logic here
END IF

And finally committing the changes to the database.

// commit
IF sqlca.of_commit( ) < 0 THEN
    ls_msg = sqlca.sqlerrtext
    sqlca.of_rollback()
    messagebox('Database Error','Error saving changes ~n~r' + ls_msg)
    //returns here
End IF
]]>