PBToolboxAI v5 ← Site

form — u_pbt_form #

← Component reference · Guide contents

form is a whole data-entry screen in one control: typed fields — text, number, list, radio buttons, date, time, colour, check box, password, e-mail, long text, picture, tags, stars — laid out in columns and in sections that fold, each with its label floating into its border. The rules of each field (required, bounds, pattern, mask…) and your business errors are checked together; of_is_dirty says what changed since the load, of_load_row / of_save_row read and write a row of a DataWindow. Your own OK button calls of_validate.

▶ See it live — Demo application, Form tile: the preview, the code behind it and this page, side by side.


At a glance #

Userobjectu_pbt_form
Item classesn_pbt_form_section (section) → n_pbt_form_field (field)
Used forReplacing a data-entry screen made of dozens of controls placed by hand with a themed form that lays itself out and checks what is typed: a customer record, an order, settings, one step of a wizard
ValuesAlways text, one by one (of_field(address).is_value); all at once and typed through of_values; a row of a DataWindow through of_load_row / of_save_row
Opt-in optionsib_veto_changes

Quick start #

// Two columns ; a name at the top, then a section
uo_form.ii_columns = 2
uo_form.of_add_field(/*keys*/ "name", /*label*/ "Name", /*type*/ u_pbt_form.TYPE_STRING, /*value*/ "")
uo_form.of_field(/*keys*/ "name").ib_required = true
uo_form.of_add_section(/*key*/ "order", /*title*/ "Order")
uo_form.of_add_field(/*keys*/ "order/qty", /*label*/ "Quantity", /*type*/ u_pbt_form.TYPE_NUMBER, /*value*/ "1")
uo_form.of_add_field(/*keys*/ "order/due", /*label*/ "Due on", /*type*/ u_pbt_form.TYPE_DATE, /*value*/ "")
// event clicked of cb_ok
// Local variables
n_pbt_json lnv_json

// Every error shown at once, the keyboard on the first
if uo_form.of_validate() > 0 then return

// Valid : read the values, typed, along the same addresses
lnv_json.of_load(/*json*/ uo_form.of_values())
of_save_order(lnv_json.of_get_string(/*path*/ "name"), lnv_json.of_get_number(/*path*/ "order/qty"))

One address per field #

A field is named by its address: its key alone at the top ("name"), the section then the field joined by / inside a section ("order/qty") — one level, never more. A section is named by its key alone. At the top, a key names one thing only: a section and a top field cannot share it. The events carry the same address, the one you wrote; the key of a field (its last level) is also the name of the column of_load_row and of_save_row read.

// A section by its key, a field by its address
uo_form.of_section(/*key*/ "order").ib_collapsed = true
uo_form.of_field(/*keys*/ "order/qty").is_value = "3"

Field types #

The type is chosen once, in of_add_field, and gives the control. The value itself is always text — given, read, handed by the events — written the same way whatever the language of the workstation. A list, a date and a colour open in the native windows shared by the whole library, at the colours of the theme.

ConstantControlThe value, as text
TYPE_STRINGText box; a mask (is_mask, the syntax of an EditMask) shows its literals while typing; a « … » button (ib_ellipsis) opens your searchThe text as is; masked, without its literals (5551234567)
TYPE_TEXTAREAText over several lines; Enter starts a new lineThe text, line breaks included
TYPE_NUMBERText box that reads a number in the display language: its decimal separator or a dot, its well-formed thousands grouping (1,000 is a thousand in English, 1.500 fifteen hundred in German, 1 234,5 in French). An ambiguous entry (1,5 in English, 1 2) or one that is not a number stays on screen with the message Not a number, translated, and the value in place does not move; Up / Down raise or lower it by its step (id_step)With a dot: "12.5"; "" = empty. Kept digit for digit; 12.50 retyped for 12.5 is no change
TYPE_BOOLEANA switch (the default) or a check box (is_boolean_style), its label beside it"true" · "false"
TYPE_ENUMButton that opens the shared list; its options are added one by one (of_add_option)The value of the chosen option, never its text; a value no option offers is kept and shown as it is, like a DataWindow without a code table
TYPE_RADIOOne radio button per option, side by side; the arrows move from one to the nextThe value of the chosen option
TYPE_DATEButton that opens the shared calendar; the date shows in the format of the display language"yyyy-mm-dd"
TYPE_TIMEText box: 9:5 becomes 09:05; what is not a 24-hour time stays on screen with its message"hh:mm"
TYPE_COLORButton with the swatch of the colour, that opens the shared pickerThe RGB long written out: String(RGB(255, 0, 0)) = "255"
TYPE_PASSWORDMasked text boxThe text as is
TYPE_EMAILText box that checks the shape of an addressThe text as is
TYPE_IMAGEA picture, not an input: never validated, never in of_values; with ii_rowspan, it covers several rows (the photo of a record)Its source: a file, or mono: / tint: followed by a path (at the colours of the theme); an http:// address is refused
TYPE_TAGSTags as pills in a box: the user types a word then Enter, a comma or Tab; Backspace in the empty input removes the last one, each pill has its cross, Left / Right walk them. The options of the field (of_add_option) are suggestions filtered as the user types; ib_options_only makes them the only choices, ii_max_tags limits how manyThe tags joined by a comma and a space: "vip, b2b" (the value of the option for a chosen suggestion). The comma is the separator: never inside a tag; written by the code it splits, the spaces around go, the same tag twice (whatever the case) is kept once
TYPE_RATINGStars to click, ii_star_count of them (5 by default), half stars with ib_half_stars. Hovering previews without moving anything; a click on the value in place clears it. From the keyboard: arrows, Home / End, a digit, 0 or Delete. A full star takes the accent of the componentThe number of stars: "4", "3.5"; "" = no rating (0 too). Written by the code, rounded to the step and kept within the stars; a text or a negative number is refused

Validation #

Each field carries its rules: ib_required (a required check box must be ticked), is_min / is_max (a number, a date, a time), ii_max_length, is_pattern (a regular expression the whole value must follow), is_mask (a mask filled), is_match (equal to another field), and the shape of an e-mail, a number or a time. Your application adds its business errors (is_error: already taken), which stay until the user edits the field. Nothing shows in red until the user has left a field: a fresh form is not red. of_is_valid and of_invalid_keys answer without showing anything; of_validate shows everything at once and puts the keyboard on the first field in error. A hidden, disabled or read-only field is not judged by its rules: the user could not fix it.

// A customer code : two capital letters, then four digits
uo_form.of_field(/*keys*/ "code").is_pattern = "[A-Z]{2}[0-9]{4}"
uo_form.of_field(/*keys*/ "code").is_pattern_message = "Two capital letters, then four digits"

// A rule only your database knows
if of_email_taken(uo_form.of_field(/*keys*/ "email").is_value) then
    uo_form.of_field(/*keys*/ "email").is_error = "Already used by another customer"
end if

Properties #

PropertyTypeDefaultPurpose
ii_columnsinteger1Number of columns, from 1 to 12 (a value outside is brought back inside). Every section follows. A field spans more with ii_colspan, starts in a given column with ii_col, covers several rows with ii_rowspan; the other fields fill the free cells
ib_readonlybooleanfalseThe whole form read-only: every value reads, none can be changed — a record you consult. Your code can still write the values
ib_veto_changesbooleanfalseOpt-in: ask before a value of the user is written (raises ue_value_changing, which can refuse: the field shows the old value again). A value written by your code never asks
is_theme_stylestring""Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change
is_theme_modestring""Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change
il_theme_accentlong-1Accent colour of this component (-1 = the application accent, or the theme's)
is_tooltipstring""Simple tooltip shown when hovering the component
is_super_tooltip_titlestring""Title of the rich tooltip (takes precedence over is_tooltip)
is_super_tooltip_textstring""Text of the rich tooltip (rich markup accepted)
is_super_tooltip_imagestring""Image of the rich tooltip

Properties of a section — n_pbt_form_section #

PropertyTypeDefaultPurpose
is_titlestring""Title of the header. Accepts rich text markup. Empty, the header shows the key
ib_collapsedbooleanfalsetrue folds the section: its header stays, its fields hide — they keep their values and are still judged. ue_section_toggled follows, as for a click on the header. Read back live
ib_visiblebooleantruefalse takes the section, and all its fields, out of the form; they keep their values (of_values still returns them) and are not judged
is_tooltipstring""Simple tooltip shown when hovering the item
is_super_tooltip_titlestring""Title of the item rich tooltip (takes precedence over is_tooltip)
is_super_tooltip_textstring""Text of the item rich tooltip (rich markup accepted)
is_super_tooltip_imagestring""Image of the item rich tooltip
il_accentlong-1Accent of the header of the section: the line under its title takes it (-1 = the one of the component). Its fields keep their own
il_back_colorlong-1Background of the header of the section, as RGB(r,g,b) (-1 = what the theme gives)
il_text_colorlong-1Text colour of the header of the section (-1 = what the theme gives)
il_back_color_hoverlong-1Background of the header of the section under the pointer (-1 = what the theme gives)
il_text_color_hoverlong-1Text colour of the header of the section under the pointer (-1 = what the theme gives)

Properties of a field — n_pbt_form_field #

PropertyTypeDefaultPurpose
is_valuestring""The value, as text (see the types). Writing it raises nothing, like SetItem; reading it gives the current value, whoever wrote it — what is being typed counts. A value the type cannot hold (a date that does not exist, a text in a number) is refused: the old one stays and of_get_last_error says why
is_labelstring""The label, inside the field: it floats up into the border when the field has a value or the keyboard. Accepts rich text markup. A star follows the label of a required field. Empty, the key
is_placeholderstring""A hint inside an empty text field while it has the keyboard
is_helpstring""A line of help under the field (rich text markup); it hides while an error takes its place
is_errorstring""A business error of your application (already taken): shown at once, the form invalid, until the user edits the field or of_clear_errors; "" removes it. Read, it gives the message the field shows — or will show once the user leaves it: the business error, else the one of its rules, "" when it is valid. On a field the user cannot reach (hidden, disabled), it is kept and reads back, but does not count: the form stays valid until the field is back
is_minstring""The smallest value accepted for a number (with a dot), a date (yyyy-mm-dd) or a time (hh:mm); the message says Minimum and the bound. A bound the type cannot read is ignored; "" = none — for a number, the low end of the SpinRange of its DataWindow column (of_load_row). Read, it gives the bound in force
is_maxstring""The largest value accepted, read like is_min; the message says Maximum and the bound
is_patternstring""A regular expression (JavaScript syntax) the whole value must follow: [A-Z]{2}[0-9]{4} accepts AB1234, not xAB1234. An empty value is left to ib_required
is_pattern_messagestring""The message when is_pattern fails; empty: Invalid format, translated
is_maskstring""An input mask. On a TYPE_STRING, the syntax of a PowerBuilder EditMask: # a digit, ! any character in upper case, ^ any character in lower case, a a letter or a digit, X any character, the rest written for the user as he types ((###) ###-####); the caret stays after what was typed. The value keeps only what was typed in the slots, as an EditMask does (5551234567, ready for a column of digits); a value of the code is the raw value, without its literals (312345678 under +33 # ## ## ## ##), the leading literals are written as soon as the field takes the keyboard, a value that does not fill every slot is Incomplete, and setting a mask after a value changes nothing the user did. After of_load_row, an empty mask takes the EditMask of the column. On a TYPE_NUMBER: a numeric mask in the PowerBuilder syntax (EditMask, DataWindow format): # an optional digit, 0 a required digit, , the grouping, . the decimal point, % the number shown times 100, literals ($, a quoted text, \x) and sections positive;negative ("$#,##0;($#,##0)"). The comma and the dot stand for the separators of the display language. While typing, the grouping appears as the digits come, the decimals are limited to the mask, letters are refused, the caret keeps its place among the digits and a pasted number is read in the display language; leaving the field completes it (12 becomes 12.00). The value keeps the number as the code writes it ("1234.5"), with no grouping and no symbol. A lower bound (is_min) of 0 or more refuses the minus sign; the bounds apply when the user leaves the field, and their message shows them through the mask; empty on a TYPE_NUMBER (the default): the field types its is_format stripped of its literals ($#,##0.00 is typed #,##0.00, #,##0 "px" is typed #,##0; a % stays, it is a scale)
is_formatstring""A TYPE_NUMBER: its display format, how the number is shown while the field does not have the keyboard — the same syntax as is_mask, with its literals: #,##0 "px" shows 575 px, $#,##0.00;($#,##0.00) a price, 0.0% a rate (0.125 shows 12.5%). When the field takes the keyboard its text becomes that of is_mask (575), the caret keeping its place among the digits; leaving it brings the display text back. The swap never changes the value and raises nothing: ue_value_changed only reports what the user typed, the bare number. Empty: the field shows its is_mask
is_boolean_stylestring"switch"A TYPE_BOOLEAN: how it is drawn. BOOLEAN_SWITCH (the default): a switch, a track and a knob that slides to the end, in the accent of the component, when it is on — an on / off setting. BOOLEAN_CHECKBOX: the classic check box — an agreement, a choice among options. Both take Space and a click on their label, give the same value and the same events; disabled or read-only, they stay readable. The knob slides in a quarter of a second, not at all when Windows asks for fewer animations; in a right-to-left language it slides to the left
ii_max_tagsinteger0A TYPE_TAGS: the largest number of tags (0 = no limit). One more typed by the user is refused: it stays in the input with the message At most N; a longer list written by the code is kept, and the field is invalid (of_is_valid, is_error) — shown once the field is touched or after of_validate, never at once
ib_options_onlybooleanfalseA TYPE_TAGS: true keeps its tags to the options of the field (of_add_option) — a text that names none (by its text or its value, without case or accent) is refused with Not in the list. false: any text is a tag, the options are only suggestions
ii_star_countinteger5A TYPE_RATING: how many stars, from 1 to 10 (a number outside is brought back inside). The value in place is kept within them, silently: set the count before a value above five
ib_half_starsbooleanfalseA TYPE_RATING: true lets the user give half stars — the first half of the star under the pointer (in the reading direction), Left / Right by half a star — and the value is written "3.5". Turned off, the value in place is rounded to a whole star, silently: turn it on before a half value
is_matchstring""The address of another field this one must equal — repeat a password, an e-mail. The message follows every change of either
is_prefixstring""A short text inside the field, before the value (a currency sign); it is not part of the value
is_suffixstring""A short text inside the field, after the value (a unit); it is not part of the value
ii_max_lengthinteger0The longest text accepted (0 = no limit) by a TYPE_STRING, TYPE_TEXTAREA, TYPE_PASSWORD or TYPE_EMAIL — a number or a time has no length, ignored; 0 takes the Limit of its DataWindow column (of_load_row), read it gives the length in force: typing stops there, a counter shows under a text field; a longer value written by the code is Too long. On a TYPE_TAGS: the longest tag — a paste of several tags is never cut, each one is judged
ii_colspaninteger1How many columns the field spans (at most ii_columns); -1 = the whole width
ii_rowspaninteger1How many rows the field spans — a picture beside several fields; the next ones fill the free cells beside it
ii_colinteger0The column the field starts in, from 1; 0 = where the flow puts it. The Tab key follows the order of the screen — the top fields first, then each section —, whatever the column
ib_requiredbooleanfalseThe field must be filled — a check box must be ticked (an I accept box), a tags field must hold one tag, a rating one star. A star follows its label; the error Required shows once the user leaves the field or at of_validate. A column whose edit style says Required makes it required too (of_load_row); read, it gives the state in force
ib_readonlybooleanfalseShows the value without letting it change: a text can still be selected and copied; a list, a date, a colour, a switch or radio buttons keep the keyboard (Tab reaches them, a screen reader says read-only) but neither open nor change. Not judged by its rules; your code can still write is_value
ib_enabledbooleantruefalse greys the field out: no keyboard (it leaves the Tab order), no click, not judged by its rules
ib_visiblebooleantruefalse takes the field out of the form; it keeps its address and its value (of_values still returns it) and is not judged
is_columnstring""The column of the DataWindow of_load_row and of_save_row read and write for this field; "" (the default) = its key, the last level of its address. Two sections can then hold the same key (billing/town, shipping/town) bound to two columns, and a field can keep a readable key for a column named cust_nm
id_stepdouble0A TYPE_NUMBER: what Up / Down (and its buttons, ib_spin) add or take away, kept within is_min and is_max; 0 (the default) = the SpinIncr of its DataWindow column (of_load_row), else 1. A step is a gesture of the user: ue_value_changed is raised (and ue_value_changing asked first with ib_veto_changes). An empty field starts at is_min, else 0; a negative step is refused
ib_ellipsisbooleanfalseA text field (TYPE_STRING, TYPE_NUMBER, TYPE_EMAIL…): true shows a … button at its end — your search, a customer, an article. A click on it, or Ctrl+Enter in the field, raises ue_ellipsis_clicked; it stays out of the Tab order. It works on a read-only field too (a value chosen by the search only), never on a disabled one
ib_spinbooleanfalseA TYPE_NUMBER: true shows two small buttons at its end, up and down, that add or take away id_step within is_min and is_max — a gesture of the user (ue_value_changed). The wheel steps it too while the field has the keyboard; the buttons stay out of the Tab order (Up / Down in the field do the same) and hide on a read-only field. A column whose EditMask says Spin gives them through of_load_row, with its SpinIncr as the step and its SpinRange as the bounds. Ignored by the other types; read, it gives the state in force
of_type ( ) → string——The type given to of_add_field (a TYPE_* constant). Read only: a type is chosen once
of_is_dirty ( ) → boolean——TRUE when this field differs from its reference (see of_is_dirty of the form)
of_is_valid ( ) → boolean——TRUE when this field carries no error; is_error says which one
is_tooltipstring""Simple tooltip shown when hovering the item
is_super_tooltip_titlestring""Title of the item rich tooltip (takes precedence over is_tooltip)
is_super_tooltip_textstring""Text of the item rich tooltip (rich markup accepted)
is_super_tooltip_imagestring""Image of the item rich tooltip
il_accentlong-1Accent of the field: its border takes it while it has the keyboard, and its label once floated up (-1 = the one of the component)
il_back_colorlong-1Background of the field, as RGB(r,g,b) (-1 = what the theme gives)
il_text_colorlong-1Text colour of the field (-1 = what the theme gives)
il_back_color_hoverlong-1Background of the field under the pointer (-1 = what the theme gives)
il_text_color_hoverlong-1Text colour of the field under the pointer (-1 = what the theme gives)

Methods #

On the component #

MethodPurpose
of_add_section (string as_key, string as_title) → longAdds a section at the end: a header that folds, underlined with the accent, under which the fields "key/name" are then added. An empty title shows the key. Returns 0 once added, -5 for an empty key, a key with / or `, or one already taken by a section or a top field, -2` when the component is not created
of_add_field (string as_keys, string as_label, string as_type, string as_value) → longAdds a field at its address, at the end of what holds it: its section or the top of the form. as_type is a TYPE_* constant, as_value its value as text (the source of a picture). Returns 0 once added, -5 for an address already taken (a field, or a section at the top), an empty level, more than two levels, a section never added, a key with `, an unknown type or a value the type cannot hold (of_get_last_error says why), -2` when the component is not created
of_add_option (string as_keys, string as_value, string as_text) → longAdds one option, at the end, to a TYPE_ENUM list, to TYPE_RADIO buttons or to the suggestions of a TYPE_TAGS: as_value is what the field holds (and what ue_value_changed gives), as_text what the user reads, as plain text ("" shows the value). Returns 0 once added, -5 for an address that is not a list, radio buttons or tags, a value already offered, a value with ` or / (of_keys_at joins it to the address of the field) — and for tags an empty value or one with a comma —, -2` when the component is not created
of_remove_option (string as_keys, string as_value) → longRemoves one option of a list, radio buttons or tags, by its value; the value of the field stays as it is. Returns 0 once removed, -5 for an address that is not a list, radio buttons or tags, or a value it does not offer, -2 when the component is not created
of_clear_options (string as_keys) → longRemoves every option of a list, radio buttons or tags, to load another list without losing the field, its handle or its state; its value stays as it is. Returns 0 once emptied, -5 for an address that is not a list, radio buttons or tags, -2 when the component is not created
of_remove_field (string as_keys) → longRemoves one field by its address, with its handle; the rest of the form keeps its state. Returns 0 once removed, -5 for an address never added, -2 when the component is not created
of_remove_section (string as_key) → longRemoves a section and all its fields, with their handles. Returns 0 once removed, -5 for a section never added, -2 when the component is not created
of_clear ( ) → longEmpties the form: sections, fields and their handles. Unlike of_reset, the columns, the read-only state and the question stay. Returns 0 once emptied, -2 when the component is not created
of_section (string as_key) → n_pbt_form_sectionThe handle of one section, by its key: title, fold, visibility, colours and tooltip. Created on first access
of_field (string as_keys) → n_pbt_form_fieldThe handle of one field, by its address: of_field("order/qty").is_value = "3". Created on first access
of_values ( ) → stringEvery value at once, read live, as a JSON object: the top fields, then each section as an object of its own — hidden ones included, pictures left out; what the user is still typing is committed first, as AcceptText does: ue_value_changing asked with ib_veto_changes, then ue_value_changed raised — both before the read, never in the middle of it: your OK reads what is on screen. Typed: a number is a number (written digit for digit, null when empty), a boolean a boolean, a colour its RGB long, the rest text. Read it with n_pbt_json, along the same address: lnv_json.of_get_number("order/qty")
of_load (string as_json) → longLoads a record: a JSON object of values by address — the top fields, then an object per section, or "order/qty" keys —, the shape of_values gives back; numbers and booleans may be typed — a number of more than 15 digits is read digit for digit, a boolean is true, false, 1 or 0 (anything else is refused). The values are written silently and become the reference of of_is_dirty; the errors shown and the business errors are cleared. A field the object does not name keeps its value. Returns 0 once loaded, -5 for a text that is not a JSON object, or a value its field cannot hold (abc for a number) — nothing is written then, of_get_last_error names the fields —, -2 when the component is not created
of_load_row (powerobject apo_data, long al_row) → longLoads one row of a DataWindow, a DataStore or a DataWindowChild: each field takes the value of its column — the one is_column names, else its key (the last level of its address) —, typed — a date yyyy-mm-dd, a time hh:mm, a number with a dot, a boolean read through the On value of a check box column (else 1, Y, true…). What the DataWindow says of the column comes along, read again at every load: a field with no label shows the header text of its column, a field with no mask nor format takes its EditMask and its Format (a number $#,##0), a list or radio buttons with no option of their own offer its code table or the rows of its DropDownDataWindow; a field with no rule of its own becomes required when the edit style says Required and limited to its Limit, and a number whose EditMask says Spin gets its buttons (ib_spin), its step (SpinIncr) and its bounds (SpinRange). The next row of the same DataWindow draws no field again: the one with the keyboard keeps it. Like of_load, the values become the reference. Returns 0 once loaded, -5 for an object that is not a DataWindow, a DataStore or a DataWindowChild, a row that does not exist, or a value its field cannot hold (nothing changes then: neither a value nor what the DataWindow says), -2 when the component is not created
of_save_row (powerobject apo_data, long al_row) → longWrites the values the user changed (since the reference: of_load_row, of_reset_update) into one row of a DataWindow, a DataStore or a DataWindowChild: each changed field into its column (is_column, else its key), by SetItem, typed for the column (date, datetime, time, number, text; a boolean as the On / Off value of a check box column, 1 / 0 in a number column, true / false in a text one; an empty field writes a NULL — an empty text, a NULL where its column says NilIsNull, an empty string otherwise, as the DataWindow does). A field the user did not change is not written: the row keeps its status, your Update sends only what changed. A TYPE_DATE keeps the time of a datetime column, a TYPE_TIME its date. What the user is still typing is committed first, and ue_value_changed raised, before anything is read; the Update stays yours. Returns 0 once written, -5 for an object that is not a DataWindow, a DataStore or a DataWindowChild, or a row that does not exist, -4 when a column was left as it was — a SetItem refused, a field whose type the column cannot hold (a TYPE_STRING in a date column), or a value with decimals for a whole-number column (3.5 stars in a long: SetItem would round it): of_get_last_error names the first one, the other changed fields are written all the same —, -2 when the component is not created
of_is_valid ( ) → booleanTRUE when no field shown carries an error: every required field filled (a required check box ticked), every rule followed, no business error. Read live, without showing anything; a business error counts only on a field the user can reach (shown and enabled)
of_invalid_keys (ref string as_keys[]) → longFills as_keys[] with the addresses of the fields in error, in screen order, without showing anything, and returns their count (0 = the form is valid)
of_validate ( ) → longShows every error at once and puts the keyboard on the first field in error (ue_focus_changed) — what your OK button calls before saving. The keyboard leaves the button for the form: the next key goes to the field; what the user is still typing is committed first, and a screen reader reads the messages at once. Returns the count of fields in error (0 = the form is valid, nothing moves), -2 when the component is not created
of_clear_errors ( ) → longClears every business error (is_error written by your code); the errors of the rules stay. Returns 0 once applied, -2 when the component is not created
of_is_dirty ( ) → booleanTRUE when at least one field differs from its reference: the value given at the add, or what of_load, of_load_row or of_reset_update made it; what the user is still typing is committed first. A number written otherwise (12.50 for 12.5) is the same value
of_dirty_keys (ref string as_keys[]) → longFills as_keys[] with the addresses of the fields changed since the reference, in screen order, and returns their count
of_reset_update ( ) → longThe values on screen become the reference: of_is_dirty answers FALSE again. Call it once your Update has succeeded, as ResetUpdate of a DataWindow. Returns 0 once applied, -2 when the component is not created
of_revert ( ) → longPuts every field back to its reference value, silently, and hides the errors — the user's Cancel. Returns 0 once applied, -2 when the component is not created
of_focus_field (string as_keys) → longPuts the keyboard on a field, like a click: its folded section opens first (ue_section_toggled), ue_focus_changed follows. Returns 0 once done, -5 for an address never added or a field the user cannot reach (hidden, disabled — the one that had the keyboard included —, a picture, beyond the demo cap), -2 when the component is not created
of_current_key ( ) → stringAddress of the field that has the keyboard — or had it last, while the keyboard is elsewhere in the window: "order/qty", "" if none yet. Always the current one, read from the component; also what ue_focus_changed hands you
of_get_last_error ( ) → stringWhy the last write was refused: a value the type cannot hold (is_value has no return code; an http:// picture, an infinite number such as 1e400), an is_pattern the form cannot read, a value of_load refused, a column of_save_row left. Empty when it went through; otherwise the last error of the library, as for every component
of_reset ( ) → longEmpties the form then puts every property of the component back to its default: one column, neither read-only nor question. Returns 0 once applied, -2 when the component is not created

On a section and on a field #

A section holds fields; a list or radio buttons hold their options, named by their value. The three methods walk them.

MethodPurpose
of_count ( ) → longNumber of fields of the section, hidden ones included; number of options of a list, radio buttons or tags (0 for the other types)
of_keys_at (long al_index) → stringAddress of the field at rank al_index (from 1) — "order/qty", ready to go back into of_field; on a list, the address followed by the value of the option. "" out of range
of_has (string as_key) → booleanDoes the section hold this field? Does the list offer this value?

Events #

EventRaised when
ue_value_changed (string as_keys, string as_value)The user changed a value: an entry confirmed by leaving the field or by Enter, a check box ticked, a radio button, an option, a date or a colour chosen (Up / Down walk a list, Delete empties a list, a date or a colour). as_keys is the address of the field, as_value its new value as text; it is already written. A value your code writes raises nothing, like SetItem. What the user is still typing when your code reads the form (of_values, of_save_row, is_value…) is committed then, as AcceptText does: the event is raised at that point, before the read
ue_value_changing (string as_keys, string as_value) → booleanCancelable, asked before a value of the user is written, and only when ib_veto_changes = true (off by default). Returning FALSE refuses: the field shows the old value again, which of_field(as_keys).is_value still returns during the event. Return TRUE to accept. When your code reads the form (of_values, of_validate, is_value…) while the user is still typing in a field, the question is asked at once, before the read: the read gives the value you allowed
ue_focus_changed (string as_from_keys, string as_keys)The field that has the keyboard changed: a click, the Tab key, your code (of_focus_field, of_validate — an order raises it too, like SetColumn), or the field left: removed, hidden, cleared (as_keys is then ""). The keyboard leaving the form for another control of the window raises nothing
ue_section_toggled (string as_key, boolean ab_collapsed)A section folded (ab_collapsed = TRUE) or unfolded: a click on its header (Enter or Space when it has the keyboard), its ib_collapsed set by your code, or of_focus_field on a field of a folded section
ue_ellipsis_clicked (string as_keys)The … button of a field (ib_ellipsis) was clicked, or Ctrl+Enter pressed in the field: open your search (a customer, an article) and write what the user chose into of_field(as_keys).is_value. What was typed is already committed (ue_value_changing asked first with ib_veto_changes, ue_value_changed raised). Never raised for a disabled field
ue_ready ( )The component has finished loading; everything sent beforehand has been replayed
ue_runtime_missing ( )The WebView2 runtime is missing: the component stays empty
ue_bg_color (long al_color)The component has computed its theme background color; the userobject has already adopted it (backcolor)

Keyboard and mouse #

GestureEffect
Tab, Shift+TabMove from field to field, in screen order: the top fields, then each section, its header first; a disabled field is skipped; radio buttons are one stop, the arrows choose
Enter in a one-line fieldConfirms the entry then goes on to the window: its default button (your OK) reads a form that is up to date. With ib_veto_changes, the page asks nothing: the entry stays pending, and the read of the default button commits it (ue_value_changing asked then) — with no default button, leaving the field commits it
EscapeIn a field the user changed, puts its value back; otherwise goes on to the window: its Cancel button
List, date, colour: Enter, Space, F4, Alt+DownOpen the shared window (list, calendar, colour picker); Enter stops there, it does not go on to the default button of the window — nor on a section header, which it folds or unfolds
List: Up / DownPrevious / next option, without opening the list
List, date, colour: Delete, BackspaceEmpty the value
Tags: Enter, comma, TabAdd what is typed (Tab keeps the keyboard in the field); Backspace in the empty input removes the last tag; Left / Right walk the tags, Delete removes the one marked; Down opens the suggestions, Escape closes them
Stars: arrows, Home / End, digits, DeleteRight / Up one star more (half a star with ib_half_stars), Left / Down one less — swapped in a right-to-left language; Home one star, End all of them; a digit that many stars; 0, Delete or Backspace clear the rating
Number: Up / DownAdd or take away the step of the field (id_step, 1 by default), kept within is_min and is_max: ue_value_changed follows. With ib_spin, its two buttons do the same, and the wheel while it has the keyboard
Ctrl+Enter, click on …A field with ib_ellipsis: commits what is typed then raises ue_ellipsis_clicked — your search
Click on a section headerFolds or unfolds the section (ue_section_toggled)

Examples #

A customer record #

// The whole form in one paint, two columns
uo_form.of_set_redraw(/*on*/ false)
uo_form.ii_columns = 2

// Identity : two required names, an e-mail, a phone with its mask
uo_form.of_add_section(/*key*/ "identity", /*title*/ "Identity")
uo_form.of_add_field(/*keys*/ "identity/first_name", /*label*/ "First name", /*type*/ u_pbt_form.TYPE_STRING, /*value*/ "")
uo_form.of_field(/*keys*/ "identity/first_name").ib_required = true
uo_form.of_add_field(/*keys*/ "identity/last_name", /*label*/ "Last name", /*type*/ u_pbt_form.TYPE_STRING, /*value*/ "")
uo_form.of_field(/*keys*/ "identity/last_name").ib_required = true
uo_form.of_add_field(/*keys*/ "identity/email", /*label*/ "E-mail", /*type*/ u_pbt_form.TYPE_EMAIL, /*value*/ "")
uo_form.of_add_field(/*keys*/ "identity/phone", /*label*/ "Phone", /*type*/ u_pbt_form.TYPE_STRING, /*value*/ "")
uo_form.of_field(/*keys*/ "identity/phone").is_mask = "(###) ###-####"

// Account : a credit limit with a bound, shown as a price and typed without the
// dollar sign ; a list ; a switch
uo_form.of_add_section(/*key*/ "account", /*title*/ "Account")
uo_form.of_add_field(/*keys*/ "account/credit_limit", /*label*/ "Credit limit", /*type*/ u_pbt_form.TYPE_NUMBER, /*value*/ "2500")
uo_form.of_field(/*keys*/ "account/credit_limit").is_format = "$#,##0.00"
uo_form.of_field(/*keys*/ "account/credit_limit").is_max = "50000"
uo_form.of_add_field(/*keys*/ "account/country", /*label*/ "Country", /*type*/ u_pbt_form.TYPE_ENUM, /*value*/ "gb")
uo_form.of_add_option(/*keys*/ "account/country", /*value*/ "gb", /*text*/ "United Kingdom")
uo_form.of_add_option(/*keys*/ "account/country", /*value*/ "fr", /*text*/ "France")
uo_form.of_add_field(/*keys*/ "account/vip", /*label*/ "VIP customer", /*type*/ u_pbt_form.TYPE_BOOLEAN, /*value*/ "false")

// Shown at once, the keyboard on the first field
uo_form.of_set_redraw(/*on*/ true)
uo_form.of_focus_field(/*keys*/ "identity/first_name")

Editing a row of a DataWindow #

The key of each field is the name of its column: the form reads the row, the user edits it, the form writes it.

// The current row of dw_customers fills the form ; nothing is dirty yet
uo_form.of_load_row(/*data*/ dw_customers, /*row*/ dw_customers.GetRow())
// event clicked of cb_save
if uo_form.of_validate() > 0 then return

// Write the values back into the row, then save it
if uo_form.of_save_row(/*data*/ dw_customers, /*row*/ dw_customers.GetRow()) < 0 then
    MessageBox("Save", uo_form.of_get_last_error())
    return
end if

// Saved : what is on screen is the new reference
if dw_customers.Update() = 1 then
    COMMIT USING SQLCA;
    uo_form.of_reset_update()
end if

Tags and stars #

A product review: a rating out of five with half stars, and free tags with suggestions. Both values stay text, which of_save_row writes into a number column and a text column.

// Local variables
string ls_score, ls_tags

// The score : five stars, half stars set BEFORE a half value
uo_form.of_add_field(/*keys*/ "score", /*label*/ "Overall", /*type*/ u_pbt_form.TYPE_RATING, /*value*/ "")
uo_form.of_field(/*keys*/ "score").ib_half_stars = true
uo_form.of_field(/*keys*/ "score").is_value = "3.5"

// Tags : free text, the options are suggestions, five at most
uo_form.of_add_field(/*keys*/ "tags", /*label*/ "Tags", /*type*/ u_pbt_form.TYPE_TAGS, /*value*/ "office, led")
uo_form.of_add_option(/*keys*/ "tags", /*value*/ "eco", /*text*/ "Eco-friendly")
uo_form.of_add_option(/*keys*/ "tags", /*value*/ "usb", /*text*/ "USB powered")
uo_form.of_field(/*keys*/ "tags").ii_max_tags = 5

// Read back : "3.5" and "office, led"
ls_score = uo_form.of_field(/*keys*/ "score").is_value
ls_tags = uo_form.of_field(/*keys*/ "tags").is_value

Warning before changes are lost #

// event closequery of the window
if not uo_form.of_is_dirty() then return 0

// Changes since the load : ask, and put them back if the user gives up
if MessageBox("Customer", "Discard your changes ?", Question!, YesNo!) = 2 then return 1
uo_form.of_revert()
return 0

Good practice #

Inherited from the common base #

These members exist on every visual component — they are not specific to this one. They are detailed once, in the transverse chapters; this table only says where to read them.

MembersRoleDetailed in
of_count · of_keys_at · of_hasWalk what the component holds3.2 Items
of_resetPut the component back to zero3.6 Resetting a component: of_reset()
of_register_shortcut · of_clear_shortcutsThe component's keyboard chords3.5 Keyboard shortcuts
of_is_created · of_is_ready · of_get_last_errorWhether it was born, whether it is ready, what failed3.7 Diagnostics
of_save_as_png · of_save_as_jpgExport the rendering as an image3.8 Exporting the rendering as an image
of_set_redrawGroup changes into a single repaint3.10 Best practices
of_preload_iconsIcons shown with no delayInstant display: of_icon
of_set_translationTranslate one of the component's labels5.2 Adapting a label: of_set_translation
of_focus_webviewGive the component the focus6.4 Keyboard and focus
of_print · of_print_to_pdfPrint, or write a PDF6.9 Printing
of_set_property · of_get_property · of_component_nameDriving a property by its name3.1 The property engine
is_accessible_name · is_accessible_descriptionThe name and description a screen reader says3.4 Optional properties and events

Two helpers are not inherited: of_icon and of_escape_markup live on n_pbt_utils. Declare one — n_pbt_utils lnv_utils, nothing to create — and call them on it.

← Component reference · Guide contents