Tech Articles


Simple User Preference/Customization Service for PowerBuilder Applications


User Preference/Customization Service

Users nowadays expect applications to remember settings and customizations between sessions.  This saves time, frustration, and improves productivity.

Here is an easy to implement, user customization service you can add to your Powerbuilder applications which stores configuration information in a database.  This gives the added benefit of portability to the users as nothing is stored in ini files or the registry.  It is PFC based but, with minor modifications, could be un-coupled from that if desired.

The main object is the n_cst_dbinifile NVO.  The export of it can be found towards the end of the article

Note the export makes use of the PFC n_cst_inifile and n_base NVOs but this can be stripped out if desired.

The service allows for default values to be set up and used if the user does not have their own set up (or when you give them the option to undo all their changes).  The service also keeps itself updated by retrieving the datastore at an interval of your choosing.

Database table (SQL Server)
You will need the following table added to your database:

create table cmnIniFile (
fileName varchar(255) NULL,
userCode varchar(255) NULL,
sectionName varchar(32) NULL,
keyName varchar(255) NULL,
valusString varchar(8000) NULL
)

The dataobject is called d_dbinifile and has two retrieval args:
as_filename (string)
as_userCode (string)

The SQL for the dataobject is:

SELECT cmnIniFile.filename,
         cmnIniFile.userCode,
         cmnIniFile.sectionName,
         cmnIniFile.keyName,
         cmnIniFile.valueString
    FROM cmnIniFile
   WHERE ( cmnIniFile.filename = :as_filename ) AND
         ( ( cmnIniFile.userCode = :as_userCode ) OR
         ( cmnIniFile.userCode = 'DEFAULT' ) )
ORDER BY cmnIniFile.filename ASC,
         cmnIniFile.userCode ASC,
         cmnIniFile.sectionName ASC,
         cmnIniFile.keyName ASC

Implementation

In the application manager object (n_cst_appmanager) for the application you
need:

n_cst_dbIniFile inv_userIniFile // instance variable

and a method (of_create_ini) which is called as part of application start up.

long ll_rc
string ls_appName
ls_appName = of_getAppName () //set in constructor by of_setappname method

inv_userIniFile = create n_cst_dbIniFile
ll_rc = inv_userIniFile.of_register (SQLCA, ls_appName, gs_username) //gs_username set at log in
if ll_rc < 0 then
    messagebox ("Application Open Error", "Unable to register the user's DB INI file.")
end if
return ll_rc

In the destructor event of the app manager object you should destroy the inv_userIniFile object.

In various windows with user specified settings which you wish to implement:

string ls_flag
ls_flag = gnv_app.inv_userIniFile.of_getString('Defaults','Maximized','N')// arguments: section, value, default
// do some processing based on the ls_flag value
// get another setting
ls_flag = gnv_app.inv_userIniFile.of_getString('Defaults','Grid Order','N')
// do some other processing based on the ls_flag value

Now we want to save settings (window size and position in this example)

gnv_app.inv_userIniFile.of_setString(this.title, 'xpos', string(this.X))
gnv_app.inv_userIniFile.of_setString(this.title, 'ypos', string(this.Y))
gnv_app.inv_userIniFile.of_setString(this.title, 'height', string(this.height))
gnv_app.inv_userIniFile.of_setString(this.title, 'width', string(this.width))

In general this would be done as part of the window close event.

Export of n_cst_dbinifile.sru


$PBExportHeader$n_cst_dbinifile.sru
$PBExportComments$INI file Service
forward
global type n_cst_dbinifile from n_base
end type
end forward

global type n_cst_dbinifile from n_base
event ue_timer pbm_timer
end type
global n_cst_dbinifile n_cst_dbinifile

type variables

public:
string is_file

protected:
n_ds ids_iniFile
n_tr itr_object
string is_filename
string is_user
long il_refresh
n_tmg itmg_timer
end variables

forward prototypes
public function integer of_register (n_tr atr_object, string as_filename, string as_user)
public function integer of_register (n_tr atr_object, string as_filename)
public function integer of_register (n_tr atr_object, string as_filename, long al_refresh)
public function string of_getstring (string as_section, string as_key, string as_default)
public function string of_getstring (string as_section, string as_key)
public function long of_getint (string as_section, string as_key, long al_default)
public function long of_getint (string as_section, string as_key)
public function long of_refresh ()
public function integer of_register (n_tr atr_object, string as_filename, string as_user, long al_refresh)
public function long of_refresh (long al_refresh)
public function integer of_setstring (string as_section, string as_key, string as_value)
public function long of_setint (string as_section, string as_key, long al_value)
public function integer of_getkeys (string as_section, ref string as_keys[])
end prototypes

event ue_timer;
// refresh the ini file based on the refresh interval set
return this.of_refresh ()
end event

public function integer of_register (n_tr atr_object, string as_filename, string as_user);
// register the ini file object with a user
return this.of_register(atr_object, as_filename, as_user, 0)
end function

public function integer of_register (n_tr atr_object, string as_filename);
// register the ini file with default values
return this.of_register(atr_object, as_filename, "", 0)
end function

public function integer of_register (n_tr atr_object, string as_filename, long al_refresh);
// register the ini file object with default values and refreshing
return this.of_register(atr_object, as_filename, "", al_refresh)
end function

public function string of_getstring (string as_section, string as_key, string as_default);
// get a string from the ini file table

long ll_rows, ll_row
string ls_find, ls_value

// Back door: Really use a file
if is_file <> '' then
    return profileString(is_file, as_section, as_key, as_default)
end if

// find a string value
ll_rows = ids_iniFile.rowcount() + 1

ls_find = "usercode = '" + is_user + "'" &
        + " and sectionname = '" + as_section + "'" &
          + " and keyname = '" + as_key + "'"

ll_row = ids_iniFile.find(ls_find, 1, ll_rows)

// if found
if ll_row > 0 then

    // get value
    ls_value = ids_iniFile.getItemString (ll_row, "valuestring")
    
else

    // else...
    if len (trim (as_default)) > 0 then

        // use default value if passed in
        ls_value = as_default
        
    else
        
        // otherwise, get default value from datastore
        ls_find = "usercode = 'DEFAULT'" &
                  + " and sectionname = '" + as_section + "'" &
                  + " and keyname = '" + as_key + "'"
    
        ll_row = ids_iniFile.find(ls_find, 1, ll_rows)
    
        if ll_row > 0 then
            ls_value = ids_iniFile.getItemString (ll_row, "valuestring")
        end if

    end if

end if

// return the string
return ls_value
end function

public function string of_getstring (string as_section, string as_key);
// get a string from the ini file table, using the default value from the table
// if there is one

return this.of_getString (as_section, as_key, "")
end function

public function long of_getint (string as_section, string as_key, long al_default);
// get an integer from the ini file table

long ll_value
long ll_rows, ll_row
string ls_find, ls_value

// Back door: Really use a file
if is_file <> '' then

    return profileInt(is_file, as_section, as_key, al_default)
end if

// find an integer value for the user, section, and key
ll_rows = ids_iniFile.rowcount() + 1

ls_find = "usercode = '" + is_user + "'" &
        + " and sectionname = '" + as_section + "'" &
          + " and keyname = '" + as_key + "'"

ll_row = ids_iniFile.find(ls_find, 1, ll_rows)

// if found
if ll_row > 0 then
    
    // get the value
    ls_value = ids_iniFile.getItemString (ll_row, "valuestring")
    
else

    // else...
    if not isnull (al_default) then

        // use the default value if passed in
        ls_value = string (al_default)
        
    else
        
        // otherwise, get default value from the datastore
        ls_find = "usercode = 'DEFAULT'" &
                  + " and sectionname = '" + as_section + "'" &
                  + " and keyname = '" + as_key + "'"
    
        ll_row = ids_iniFile.find(ls_find, 1, ll_rows) + 1
    
        if ll_row > 0 then
            ls_value = ids_iniFile.getItemString (ll_row, "valuestring")
        end if

    end if

end if

// convert the value into and integer (well, really, it's a long)
if isNumber (ls_value) then
    ll_value = long (ls_value)
else
    setNull (ll_value)
end if

// return the integer
return ll_value
end function

public function long of_getint (string as_section, string as_key);
// get an integer from the ini file table using the database default value
// if there is one

long ll_null
setnull (ll_null)

return this.of_getInt (as_section, as_key, ll_null)
end function

public function long of_refresh ();
// refresh the ini file datastore, maintaining the current refresh interval

return this.of_refresh (il_refresh)
end function

public function integer of_register (n_tr atr_object, string as_filename, string as_user, long al_refresh);
// register the ini file object

// set the trans object
long ll_rc
if isNull (atr_object) or not isValid (atr_object) then return FAILURE
ll_rc = ids_iniFile.setTransObject (atr_object)
if ll_rc < 0 or isnull (ll_rc) then return FAILURE
itr_object = atr_object

// set the filename
if len (trim (as_filename)) = 0 or isnull (as_filename) then    return FAILURE
is_filename = as_filename

// set the user
if len (trim (as_user)) = 0 or isnull (as_user) then
    is_user = 'DEFAULT'
else
    is_user = upper(as_user)
end if

// set the refresh interval
if al_refresh < 0 or isnull (al_refresh) then
    il_refresh = 0
else
    il_refresh = al_refresh
end if

// retrieve
return this.of_refresh()
end function

public function long of_refresh (long al_refresh);
// if refresh interval has changed
if il_refresh <> al_refresh then

    // un-register the timer object
    itmg_timer.inv_single.of_unRegister ()

    // remember the new refresh interval
    il_refresh = al_refresh
    
    // if the refresh interval is not zero, re-register the timer object
    if il_refresh <> 0 then
        itmg_timer.inv_single.of_register (this, "ue_timer", il_refresh * 60)
    end if
    
end if

// retrieve the ini file
long ll_rc
ll_rc = ids_iniFile.retrieve(is_filename, is_user)

// register the timer object, if it hasn't been 
if not isvalid (itmg_timer) then
    itmg_timer = create n_tmg
    itmg_timer.of_setSingle(true)
    if il_refresh <> 0 then
        itmg_timer.inv_single.of_register (this, "ue_timer", il_refresh * 60)
    end if
end if

return ll_rc
end function

public function integer of_setstring (string as_section, string as_key, string as_value);
// write a string to the ini file
long ll_rows, ll_row, ll_rc
string ls_find

// Back door: Really use file
if is_file <> '' then
    return setProfileString(is_file, as_section, as_key, as_value)
end if

// find a row in the datastore to modify
ll_rows = ids_iniFile.rowcount() + 1

ls_find = "usercode = '" + is_user + "'" &
        + " and sectionname = '" + as_section + "'" &
          + " and keyname = '" + as_key + "'"

ll_row = ids_iniFile.find(ls_find, 1, ll_rows)
if ll_row < 0 then return FAILURE

// if row found
if ll_row > 0 then
    
    // update an existing value
    ids_iniFile.setItem (ll_row, "valuestring", as_value)

else
    
    // else insert a new value
    ll_row = ids_iniFile.insertRow(0)
    if ll_row <= 0 then return FAILURE
    
    ids_iniFile.setItem (ll_row, "filename", is_filename)
    ids_iniFile.setItem (ll_row, "usercode", is_user)
    ids_iniFile.setItem (ll_row, "sectionname", as_section)
    ids_iniFile.setItem (ll_row, "keyname", as_key)
    ids_iniFile.setItem (ll_row, "valuestring", as_value)
    
end if

return ids_iniFile.update()
end function

public function long of_setint (string as_section, string as_key, long al_value);
// set an integer to the ini file
string ls_value
ls_value = string(al_value)
return this.of_setString (as_section, as_key, ls_value)
end function

public function integer of_getkeys (string as_section, ref string as_keys[]);
// get keys for a section

integer li_count

long ll_rowCount, ll_row
string ls_find

// Back door: Really use a file
n_cst_inifile lnv_iniFile
if this.is_file <> "" then
    return lnv_iniFile.of_getKeys (this.is_file, as_section, as_keys)
end if

ll_rowCount = ids_iniFile.rowcount() + 1

ls_find = "usercode = '" + is_user + "'" &
        + " and sectionname = '" + as_section + "'" &

ll_row = ids_iniFile.find(ls_find, 1, ll_rowCount)
do while ll_row > 0
    li_count ++
    as_keys[li_count] = ids_iniFile.getItemString (ll_row, "keyname")
    ll_row ++
    if ll_row > ll_rowcount then exit
    ll_row = ids_iniFile.find(ls_find, ll_row, ll_rowCount)
loop

return li_count
end function

on n_cst_dbinifile.create
call super::create
end on

on n_cst_dbinifile.destroy
call super::destroy
end on

event constructor;call super::constructor;
// create the ini file datastore
ids_iniFile = create n_ds
ids_iniFile.dataObject = "d_dbinifile"
end event

event destructor;call super::destructor;
// destroy the timer object
if isvalid (itmg_timer) then destroy (itmg_timer)

// destroy the ini file datastore
if isvalid (ids_iniFile) then destroy (ids_iniFile)
end event

 

Comments (0)
There are no comments posted here yet

Find Articles by Tag

SqlModelMapper Jenkins Filter Database Painter Icon CrypterObject .NET DataStore PowerBuilder (Appeon) ODBC Elevate Conference Database Object Debug JSONGenerator SQL Server Error Configuration DataWindow JSON PostgreSQL OrcaScript TFS Service Stored Procedure Installation DevOps Performance Charts Windows OS Text PowerServer Web OAuth 2.0 COM Event Script Linux OS PFC Expression DataType .NET Assembly Repository SqlExecutor Messagging Database Table Data SQL UI Modernization RibbonBar Window Android CoderObject License Git PowerBuilder IDE Source Code File iOS PBDOM UI Themes External Functions CI/CD PBVM Export Design RESTClient Export JSON JSONParser Source Control SDK PowerBuilder Compiler RibbonBar Builder TortoiseGit WebBrowser Resize Event Handler Automated Testing Variable Array REST Web API Interface MessageBox Deployment Platform Debugging API Debugger Graph Event Handling Outlook Database Connection Web Service Proxy SnapObjects Import JSON 32-bit HTTPClient Class DLL UI Encoding OLE PowerScript (PS) 64-bit Menu PostgreSQL ODBC driver BLOB Visual Studio PBNI Authorization XML Testing JSON TreeView PDF SOAP Database Table Encryption TLS/SSL GhostScript Application Sort Transaction Import Model RichTextEdit Control WinAPI Database Validation Oracle Syntax Trial DataWindow Azure SnapDevelop Database Table Schema Mobile ActiveX .NET Std Framework NativePDF Data OAuth SVN PowerServer Mobile Excel Database Profile PDFlib C# DragDrop InfoMaker Authentication Windows 10 Icons Migration Open Source Bug Branch & Merge