Passing parameters between PB objects

I would like to share with you a small, but useful object (NVO) named nvuo_parm which helps in the very common task of passing parameters between application units (for example, between windows). It is extremely important for communication between objects to be easy for developers, type-safe (bugs-prone) and effective (quick), so nvuo_parm is a good candidate!


  1. Save the file nvuo_parm.sru on your hard disk.
  2. Import the saved file into one of the application's PBLs.


Passed parameters are added to the NVO using its public setter methods. The names of these methods are constructed by the pattern uf_set_{type} where {type} is a type identifier - one or two letters taken from the standard PB variables naming convention (like "s" for string, "i" for integer etc.). Each setter method has 2 arguments: the keyword (in fact, the "name" of the passed parameter - of course, its type is always string) and the value (its type depends on the {type}). The passed parameters are accepted by the destination object using public getter methods (uf_get_{type}) which has only one argument - the keyword - and returns the passed value (so, the getters return different types).

The following table lists all the public methods of nvuo_parm:

Data Type
Setter Getter
integer uf_set_i uf_get_i
long uf_set_l uf_get_l
decimal uf_set_dc uf_get_dc
double uf_set_db uf_get_db
real uf_set_r uf_get_r
string uf_set_s uf_get_s
char uf_set_c uf_get_c
date uf_set_d uf_get_d
time uf_set_t uf_get_t
datetime uf_set_dt uf_get_dt
boolean uf_set_b uf_get_b
any (+ arrays and structures) uf_set_a uf_get_a
NonVisualObject uf_set_nv uf_get_nv
GraphicObject uf_set_go uf_get_go
Window uf_set_w uf_get_w
DataWindow uf_set_dw uf_get_dw
DataStore uf_set_ds uf_get_ds

As you see, special getters and setters are used for for parameters of different types. This approach is type-safe, but if you prefere a generic solution (one getter and one setter for all data types) then use a class named n_parm (downloadable here) - the new, more lightweight version. It is more convenient in use but, unfortunately, not type-safe: compiler will not prevent you from getting an integer parameter into a string variable... But if that problem doesn't stop you (anyway, you will get a run-time error doing a unit test), that class is easier to use. It will not be mentioned anymore in the page you are currently reading.


Situation: we want to pass parameters from window A to window B. Write in window A (in a function which opens window B):

nvuo_parm lnv_parm

lnv_parm.uf_set_i("birth_year", ii_birth_year)
lnv_parm.uf_set_l("order_id", ll_order_id)
lnv_parm.uf_set_s("first_name", ls_first_name)
lnv_parm.uf_set_s("mid_name", ls_mid_name)
lnv_parm.uf_set_s("last_name", ls_last_name)
lnv_parm.uf_set_dt("order_date", ldt_order_date)
lnv_parm.uf_set_b("record_is_new", true)
lnv_parm.uf_set_a("last_companies_worked", ls_last_companies_worked_arr[]) // array
lnv_parm.uf_set_a("str_address", lstr_address) // structure
lnv_parm.uf_set_nv("nvo_customer_controller", inv_customer_controller)
lnv_parm.uf_set_go("cb_details", cb_details)
lnv_parm.uf_set_w("calling_window", this)
lnv_parm.uf_set_dw("dw_main", this.dw_main)
lnv_parm.uf_set_ds("ds_details", this.ids_details)

OpenWithParm(w_B, lnv_parm)

Open event of window B:

nvuo_parm lnv_parm

lnv_parm = Message.PowerObjectParm

ii_birth_year = lnv_parm.uf_get_i("birth_year")
il_order_id = lnv_parm.uf_get_l("order_id")
is_first_name = lnv_parm.uf_get_s("first_name")
is_mid_name = lnv_parm.uf_get_s("mid_name")
is_last_name = lnv_parm.uf_get_s("last_name")
idt_order_date = lnv_parm.uf_get_dt("order_date")
ib_record_is_new = lnv_parm.uf_get_b("record_is_new")
ls_last_companies_worked_arr = lnv_parm.uf_get_a("last_companies_worked") // array
istr_address = lnv_parm.uf_get_a("str_address") // structure
inv_customer_controller = lnv_parm.uf_get_nv("nvo_customer_controller")
icb_details = lnv_parm.uf_get_go("cb_details")
iw_calling_window = lnv_parm.uf_get_w("calling_window")
idw_main = lnv_parm.uf_get_dw("dw_main")
ids_details = lnv_parm.uf_get_ds("ds_details")


  1. Parameter name can contain any characters including dashes and internal spaces (external spaces are Trim()med). So, the following names are the same: "first_name", " first_name", "first_name ", " first_name ", but the following are different: "first_name", "first name", "firstname".

  2. Parameter name is NOT case-sensitive. For exmaple, "order_id", "ORDER_ID" and "Order_Id" are treated as the same parameter.

  3. Parameter name is mandatory, it cannot be NULL or empty string.

  4. Parameter value is NOT mandatory, it can be NULL or empty string.

  5. Parameter names must be unique per data type. Subsequent call of uf_set_... with a same name overrides (replaces) the existing (old) parameter having that name and data type. Let's experiment:

    uf_set_i("today", 30) // parameter of type "integer" is created
    uf_set_i("today", 31) // 30 is lost
    li_day = uf_set_i("today") // li_day contains 31
    uf_set_d("today", Today()) // absolute new parameter is created - this time of type "date"
    li_day = uf_set_i("today") // li_day still contains 31

  6. uf_get_... methods return NULL (for arrays - an array with zero upper bound) of the appropriate data type if the asked parameter doesn't exist (i.e. no parameter with that name has been ever added, or it was added and NULLified later - see the next remark).

  7. To delete a parameter, simply override it sending NULL for the value (for an array send a zero upper bound array):

    int li_null
    long ll_empty_arr[]

    lnv_parm.uf_set_i("birth_year", li_null)
    lnv_parm.uf_set_a("departments", ll_empty_arr)

  8. The class is autoinstantiated - no CREATEs and DESTROYs!

  9. It's a good idea to create in n_parm public string constants for the names of parameters, widely passed in the application, and use them with both uf_add and uf_get instead of hard-coded names. For example, if your application deals a lot with invoices then you can create a constant INVOICE_NUM (keeping the value "invoice_num") and write

    lnv_parm.uf_set_l(lnv_parm.INVOICE_NUM, ll_invoice_num) // source object's script
    il_invoice_num = anv_parm.uf_get_l(lnv_parm.INVOICE_NUM) // target object's script

    instead of

    lnv_parm.uf_set_l("invoice_num", ll_invoice_num) // source object's script
    il_invoice_num = anv_parm.uf_get_l("invoice_num") // target object's script

    So, there never will be a situation when a source sets "invoice_num" but a target tries to obtain "inv_num", "invoice_number", "invoice_id" or whatever else. To know more about NVOs, created especially for constants, read here.

  10. The maximum quantity of passed parameters of each type is 65535 (the max. value of uint). So, you can pass up to 65535 integers, up to 65535 booleans etc. There is no chance it will be not enough in a real-life application, so the object doesn't check overflow (it would downgrade performance).

  11. A transported parameter itself can be of type n_parm, so you can pass nested constructions of any depth.

  12. Methods for passing parameters of types any (uf_..._a), GraphicObject (uf_..._go) and NonVisualObject (uf_..._nv) are NOT type safe - on types mismatch you will get a run-time failure instead of a compilation error. If the passed parameter is not really of the functions' type (any, GraphicObject or NonVisualObject accordingly) then two conversions between data types are required. So, don't use them for types having their own setter/getter even if it is possible technically:

  13. You can add to nvuo_parm your own uf_set_... and uf_get_... functions for other classes if their objects are passed intensively in your application. It will make development more type-secure!

  14. A passed parameter may be itself of the type nvuo_parm, so you can pass nested constructions of any depth. For example, you can pack into one nvuo_parm data for one worker (first and last names, array of departments he has worked etc.), and create an array of such workers to be passed in the outer (main) nvuo_parm.

free counters