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 methodinv_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)