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

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