PBToolboxAI v5 ← Site

propertygrid — u_pbt_propertygrid #

← Component reference · Guide contents

propertygrid is the property window of an IDE, for your own data: named values, sorted into categories that fold, each with the editor of its type — text, number, check box, list of choices, colour, date, password. Two views (by category, alphabetical), a search box, a help pane that describes the selected property, and a … button that opens a dialog of your own. The user edits, ue_value_changed tells you.

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


At a glance #

Userobjectu_pbt_propertygrid
Item classesn_pbt_propertygrid_category (category) → n_pbt_propertygrid_property (property)
Used forReplacing a settings screen pieced together from fields and boxes with a themed, typed, searchable inspector: the settings of a document, a report, a record, an object of the application
ValuesAlways text, one by one (of_property(address).is_value); all at once and typed through of_values
Opt-in optionsib_veto_changes

Quick start #

// The settings of a report : one value at the top, then a category
uo_grid.of_add_property(/*keys*/ "title", /*label*/ "Title", /*type*/ u_pbt_propertygrid.TYPE_STRING, /*value*/ "Monthly sales")
uo_grid.of_add_category(/*key*/ "page", /*title*/ "Page")
uo_grid.of_add_property(/*keys*/ "page/landscape", /*label*/ "Landscape", /*type*/ u_pbt_propertygrid.TYPE_BOOLEAN, /*value*/ "true")
uo_grid.of_add_property(/*keys*/ "page/margin", /*label*/ "Margin (mm)", /*type*/ u_pbt_propertygrid.TYPE_NUMBER, /*value*/ "12.5")

// The help pane describes the selected property
uo_grid.of_property(/*keys*/ "page/margin").is_description = "Around the printed area, in [b]millimetres[/b]."
// event ue_value_changed of uo_grid : (string as_keys, string as_value)
// The value is already written ; the address says which one
choose case as_keys
    case "title"
        of_set_report_title(as_value)
    case "page/landscape"
        of_set_landscape(as_value = "true")
end choose

One address per property #

A property is named by its address: its key alone at the top ("title"), the category then the property joined by / under a category ("page/margin"). An expandable property (TYPE_GROUP) holds properties in turn, added at its address: "appearance/font/size" in the group font of the category appearance, "margin/left" in a group at the top — and a group can hold another one. What holds a property is therefore a category (one level only) or a group. A category is named by its key alone. At the top, a key names one thing only: a category and a top property cannot share it. The events carry the same address, the one you wrote.

// A category by its key, a property by its address
uo_grid.of_category(/*key*/ "page").ib_collapsed = true
uo_grid.of_property(/*keys*/ "page/margin").is_value = "15"

// A property held by a group : the address goes through the group
uo_grid.of_property(/*keys*/ "appearance/font/size").is_value = "11"

Value types #

The type is chosen once, in of_add_property, and gives the editor of the row. The value itself is always text — given, read, handed by the events — written the same way whatever the language of the workstation.

ConstantEditorThe value, as text
TYPE_STRINGText boxThe text as is
TYPE_NUMBERText box that accepts a number only, read in the display language: its decimal separator or a dot, its thousands grouping when well formed (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 is refused: the typed text stays, framed in red, and the help pane says why (Escape gives it up). is_min, is_max and id_step bound the entry and drive Up / DownWith a dot: "12.5"; "" = empty. Kept digit for digit ("1.50" stays "1.50"); a text that is not a number is refused
TYPE_BOOLEANCheck box"true" · "false"
TYPE_ENUMList of choices at the colours of the theme; its options are added one by one (of_add_option), their text plain, without markupThe value of the chosen option, never its text
TYPE_COLORSwatch of the colour and colour picker; the cell shows r, g, b. Above 16777215, a PowerBuilder system colour (67108864 Button Face, 553648127 Transparent, 1073741824 Window Background…) is shown by its name, with a neutral swatch, and the picker opens without a current colourThe RGB long written out: String(RGB(255, 0, 0)) = "255"
TYPE_DATECalendar; the cell shows the date in the format of the display language"yyyy-mm-dd" : String(Today(), "yyyy-mm-dd")
TYPE_PASSWORDMasked text boxThe plain text, never shown nor found by the search (even {x:raw} of a summary stays masked)
TYPE_GROUPNone: an expandable property (a font, a margin), with a chevron at the start of its label; it holds the properties added at its address and opens through ib_expanded, its chevron, a double click on its label or the keyboard. Closed when it is addedNone to give ("", any other is refused); read, its summary: the one is_summary writes, otherwise the shown values of what it holds, hidden ones skipped, joined by ; ("Segoe UI; 9; True"); written, ignored

A value its type cannot hold is refused — by of_add_property (-5) as by is_value or is_default (the old one stays) — and of_get_last_error says why: a number that is not one, a boolean other than true / false / 1 / 0, a date that is not a real yyyy-mm-dd, a colour that is not a whole number from -1, a TYPE_STRING or a TYPE_PASSWORD on several lines (the field of the grid holds one line). "" (no value) is always accepted. A list value that no option offers is accepted, and shown as it is: a list may be loaded after its value. The colour -1 — the one the theme gives, the convention of the whole library — is shown “(default)”; a system colour and the true / false of a summary are written in the display language.


Properties #

PropertyTypeDefaultPurpose
is_sortstring"categorized"The view: SORT_CATEGORIZED (the top properties, then each category under a header that folds) or SORT_ALPHABETICAL (one list sorted by label as it is shown, without its markup, and without headers; a group keeps what it holds under it). The two buttons of the toolbar change it too: read back live. Switching to alphabetical while a header is selected clears the selection
ib_toolbarbooleantrueThe toolbar: the two views and the search box. false hides it; is_sort and is_filter can still be driven by the code
ib_helpbooleantrueThe help pane, at the bottom: the label and the description (is_description) of the selected property, the title of a selected category
ii_label_widthinteger40Share of the width given to the labels, in percent, from 15 to 85. The user drags the line between labels and values: read back live
is_filterstring""The search: only the properties whose label, key or shown value contains this text, whatever the case, stay; during a search, every category and every group that hold a match show open, without losing their fold. The label is read as it is shown, without its markup; neither the title of a category nor the full address is searched. During a search, a fold of the user does nothing; a fold of your code (ib_collapsed, ib_expanded, of_expand_all, of_collapse_all) is recorded, raises its event and shows once the search is emptied; a selected row the search hides leaves the selection (ue_selection_changed with an empty address). The search box writes it too (Escape clears it): read back live. "" shows everything
ib_veto_changesbooleanfalseOpt-in: ask before a value of the user is written (raises ue_value_changing, which can refuse: the cell 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 category — n_pbt_propertygrid_category #

PropertyTypeDefaultPurpose
is_titlestring""Title of the header. Accepts rich text markup. Empty, the header shows the key
ib_collapsedbooleanfalsetrue folds the category: its header stays, its properties hide (a selection that was inside goes up to the header). ue_category_toggled follows, as for a click on the header. During a search, every category shows open: a click on its header does nothing, and true set by the code is recorded (the event follows, a read gives it back) and shows once the search is emptied. Read back live
ib_visiblebooleantruefalse takes the category, and everything in it, out of the grid; it keeps its key and its values, which of_values still returns
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 category: its selection takes it (-1 = the one of the component). To colour it entirely, il_back_color / il_text_color
il_back_colorlong-1Background of the header of the category, as RGB(r,g,b) (-1 = what the theme gives)
il_text_colorlong-1Text colour of the header of the category (-1 = what the theme gives)
il_back_color_hoverlong-1Background of the header of the category under the pointer (-1 = what the theme gives)
il_text_color_hoverlong-1Text colour of the header of the category under the pointer (-1 = what the theme gives)

Properties of a property — n_pbt_propertygrid_property #

PropertyTypeDefaultPurpose
is_valuestringthe one of of_add_propertyThe value, as text (see Value types); for a list, the value of the option. Writing it raises nothing, like SetItem; reading it gives the current value, whoever wrote it. A value its type cannot hold (a TYPE_NUMBER given a text that is not a number) is refused: the old value stays, and of_get_last_error of the grid says why. On a group (TYPE_GROUP), reading gives its summary (see is_summary), and a write is ignored
is_labelstringthe one of of_add_propertyThe name shown in the left column; accepts rich text markup. Empty, the row shows the key, as it is (never read as markup). The alphabetical view sorts on it, and the search reads it, as it is shown: without its markup
is_descriptionstring""The text of the help pane while the property is selected; accepts rich text markup
ib_readonlybooleanfalseShown, not editable: the value is greyed and no editor opens, neither by a click, nor by the keyboard, nor by a double click. A read-only group makes everything it holds read-only. The … button (ib_ellipsis) stays active: read ib_readonly in ue_ellipsis_clicked to open your dialog read-only
ib_visiblebooleantruefalse takes the row out of the grid; the property keeps its address and its value (of_values still returns it). Hiding a group hides what it holds; hiding the selected property clears the selection
ib_ellipsisbooleanfalseA … button at the end of the row: a click — or Ctrl+Enter on the selected row — selects the row and raises ue_ellipsis_clicked, even on a read-only property, for a dialog of your own (a file, a font, a list taken from the database)
ib_expandedbooleanfalseFor an expandable property (TYPE_GROUP): true shows what it holds, indented under it; false hides it (a selection that was inside goes up to the group). ue_property_toggled follows, as for a click on its chevron. During a search, a group that holds a match shows open: its chevron does nothing, and a value set by the code is recorded (the event follows) and shows once the search is emptied. Read back live; no effect on the other types
is_summarystring""For an expandable property (TYPE_GROUP): how its summary is written, a template. {size} is the shown value of its sub-property size, {size:raw} its value as stored (a password stays masked), {?bold}…{/bold} a piece kept only when bold is on (a true boolean, a value that is neither empty, false nor 0), {!bold}…{/bold} only when it is not, {{ }} literal braces; an unknown key gives nothing. "{name}, {size} pt{?bold}, bold{/bold}" shows Segoe UI, 10 pt, bold. Empty (the default): the shown values, hidden ones skipped, joined by ; . The summary is plain text; it follows every change of what the group holds; is_value of the group returns it
is_maskstring""For a TYPE_NUMBER: its input mask, 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. Empty (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), else the number as it is; no effect on the other types
is_formatstring""For a TYPE_NUMBER: its display format, how the cell shows the number while its field does not have the keyboard — the same syntax as is_mask, with its literals: #,##0 "px" shows 575 px, $#,##0.00 a price, 0.0% a rate (0.125 shows 12.5%). The summary of a group and the search read it too. 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 default): the cell shows its is_mask; no effect on the other types
is_defaultstring""The default value, as text like is_value. While the value differs from it, it is written in bold and the help pane shows the default; the user puts it back with Delete on the selected row or with the Reset button (↺) of the toolbar, shown only when at least one property has a default — a gesture of the user: ue_value_changing when the veto is on, then ue_value_changed. A value its type cannot hold is refused. Writing "" removes the default: no more bold, no default in the help pane, "" read back. Without a default, Delete empties the value (a boolean stays as it is)
is_errorstring""An error of your application ("This name already exists"): the value is framed in red, the help pane says it in words, so does a tooltip, and a screen reader announces it. It goes when the user edits the property, or when you write "". Set in ue_value_changing before refusing, it explains the refusal. Read, the error the property shows: yours, else the grid's own when a typed number is refused (not a number, out of is_min / is_max) — the typed text then stays on screen to be corrected, Escape gives it up
is_minstring""For a TYPE_NUMBER: the smallest value the user may give, as a number the code writes ("0", "12.5"); "" = no bound. A number typed below it is refused, stays on screen and the help pane says why; Down stops there. Your code may still write any value. A text that is not a number is refused (of_get_last_error), and so is a bound that crosses the other one (is_min above is_max). Ignored by the other types
is_maxstring""For a TYPE_NUMBER: the largest value the user may give; "" = no bound. Same rules as is_min; Up stops there
id_stepdouble1For a TYPE_NUMBER: what Up and Down add or take while its field has the keyboard, within is_min / is_max. Each step is a value of the user (ue_value_changed, asked first when the veto is on). 0 or less counts as 1. Ignored by the other types
of_is_dirty ( ) → boolean——TRUE when the value differs from its reference (see of_is_dirty of the grid)
of_type ( ) → string——The type given to of_add_property (a TYPE_* constant). Read only: a type is chosen once
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 row of the property: its selection and the frame of its editor take it (-1 = the one of the component). To colour it entirely, il_back_color / il_text_color
il_back_colorlong-1Background of the row of the property, as RGB(r,g,b) (-1 = what the theme gives)
il_text_colorlong-1Text colour of the row of the property (-1 = what the theme gives)
il_back_color_hoverlong-1Background of the label of the property while the pointer is over its row (-1 = what the theme gives)
il_text_color_hoverlong-1Text colour of the label of the property while the pointer is over its row (-1 = what the theme gives)

Methods #

On the component #

MethodPurpose
of_add_category (string as_key, string as_title) → longAdds a category at the end: a header that folds, under which the properties "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 category or a top property, -2` when the component is not created
of_add_property (string as_keys, string as_label, string as_type, string as_value) → longAdds a property at its address, at the end of what holds it: its category, its group ("appearance/font/size", "margin/left") or the top properties. as_type is a TYPE_* constant, as_value its value as text ("" for a TYPE_GROUP, which has none). Returns 0 once added, -5 for an address already taken, a parent that is neither a category nor a group (or not added yet), an empty level ("layout/", "a//b"), a key with `, an unknown type, a TYPE_NUMBER value that is not a number as the code writes it (a dot for the decimals, optional sign and exponent, "" allowed), a boolean, a date or a colour that is not one, a text or a password on several lines (of_get_last_error says why) or a value given to a TYPE_GROUP, -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 the list of a TYPE_ENUM property: as_value is what the property holds (and what ue_value_changed gives), as_text what the list shows, as plain text without markup ("" shows the value). Returns 0 once added, -5 for an address that is not a list property, a value this property already offers, or a value with ` or / (of_keys_at names an option by the address of the property followed by its value: one level), -2` when the component is not created
of_remove_option (string as_keys, string as_value) → longRemoves one option from the list of a TYPE_ENUM property, by its value. The value the property holds stays as it is, even when it was that option. Returns 0 once removed, -5 for an address that is not a list property, or a value its list does not offer, -2 when the component is not created
of_clear_options (string as_keys) → longRemoves every option of a TYPE_ENUM property, to load another list without losing the property, its handle, its selection or its flags. The value it holds stays as it is: write the new one in is_value. Returns 0 once emptied, -5 for an address that is not a list property, -2 when the component is not created
of_category (string as_key) → n_pbt_propertygrid_categoryHandle of one category, by its key (created on first access): its title, its fold, its visibility and its colours are set through it
of_property (string as_keys) → n_pbt_propertygrid_propertyHandle of one property, by its address (created on first access): its value, its label, its description, its flags and its colours are set and read through it. An address this grid never added (a typing error) gives a handle that is not kept: writing through it does nothing, and of_get_last_error says why
of_remove_property (string as_keys) → longRemoves one property, and its handle with it; a group leaves with everything it holds. If it was selected, ue_selection_changed announces there is no selection any more. Returns 0 once removed, -5 for an address this grid never added, -2 when the component is not created
of_remove_category (string as_key) → longRemoves a category and all its properties, with their handles. If the selection was inside, ue_selection_changed announces there is none any more. Returns 0 once removed, -5 for a key this grid never added as a category, -2 when the component is not created
of_clear ( ) → longEmpties the grid: categories, properties, options and handles. The view, the toolbar, the help pane and the search stay as they are (of_reset is the one that puts every property of the component back). If a row was selected, ue_selection_changed follows with an empty address. Returns 0 once emptied, -2 when the component is not created
of_select_property (string as_keys) → longSelects a row, like a click: a property ("page/margin") or the header of a category ("page"). What holds the property opens first: its folded category, its closed groups (ue_category_toggled, ue_property_toggled) — only when the row will then be shown: a refused call opens nothing. ue_selection_changed follows, as for a click. Returns 0 once selected (or when it already was), -5 for an address this grid never added or a row that is not shown (hidden, out of the search, beyond the demo cap, a header in the alphabetical view), -2 when the component is not created
of_clear_selection ( ) → longLeaves no row selected; ue_selection_changed follows with an empty address. Nothing selected already: nothing is raised. Returns 0 once applied, -2 when the component is not created
of_selected_key ( ) → stringAddress of the selected row, read live, whoever set it: "page/margin", "page" for a header, "" when none. Exactly what ue_selection_changed hands you
of_expand_all ( ) → longUnfolds every category and opens every group; ue_category_toggled follows for each category that was folded, ue_property_toggled for each group that was closed. Returns 0 once applied, -2 when the component is not created
of_collapse_all ( ) → longFolds every category and closes every group; ue_category_toggled and ue_property_toggled follow for each one that changes. Returns 0 once applied, -2 when the component is not created
of_dirty_keys (ref string as_keys[]) → longFills as_keys[] with the addresses of the properties that differ from their reference, in the order of the categorized view, and returns their count (0 = nothing to save); what the user is still typing is committed first
of_is_dirty ( ) → booleanTRUE when at least one property differs from its reference: the value it was added with, or what of_load or of_reset_update made it — whether the user or your code changed it. The same name as on the form. Not to be confused with is_default, the value shown in bold: this one says what there is to save. What the user is still typing is committed first, as for of_values
of_load (string as_json) → longLoads values at once: a JSON object by address, the shape of_values gives back — the top properties, an object per category and per group ({"name":"Orders","layout":{"width":800}}) — or "layout/width" keys. The values are written silently (no ue_value_changed) and become the reference of of_is_dirty; the errors shown go. A property the object does not name keeps its value, a number is read digit for digit. A value the grid cannot take (no property at that address, a value its type cannot hold) is skipped: the rest is written, and of_get_last_error names the addresses skipped. What the user was typing is given up. Returns 0 once applied (skipped values included), -5 for a text that is not a JSON object, -2 when the component is not created
of_reset_update ( ) → longThe values in place become the reference: of_is_dirty answers FALSE again. Call it once your settings are saved, like ResetUpdate of a DataWindow. Returns 0 once applied, -2 when the component is not created
of_revert ( ) → longPuts every property back to its reference value, silently (no event, like a value written by the code), and removes the errors shown; what the user was typing is given up (nothing is raised after it): the Cancel button of your settings window. Returns 0 once applied, -2 when the component is not created
of_clear_errors ( ) → longClears every error of your application (is_error of each property). A typed number the grid refused stays: the user has to correct it. Returns 0 once applied, -2 when the component is not created
of_focus_property (string as_keys) → longPuts the keyboard on a property, like a click on its value: what holds it opens first (ue_category_toggled, ue_property_toggled), it is selected (ue_selection_changed) and its editor takes the keyboard; a group or a read-only property: the grid. Returns 0, -5 for an address this grid never added or a row that cannot be shown (hidden, out of the search, beyond the demo cap), -2 when the component is not created
of_invalid_keys (ref string as_keys[]) → longFills as_keys[] with the addresses of the properties in error, in the order of the categorized view, and returns their count (0 = the grid is valid); what the user is still typing is committed first
of_is_valid ( ) → booleanTRUE when no property shown carries an error: neither yours (is_error) nor the grid's own (a typed number refused: not a number, out of is_min / is_max). What the user is still typing is committed first: a refused text counts. The same name as on the form
of_validate ( ) → longPuts the keyboard on the first property in error — its category and its groups open, it is selected and its editor takes the keyboard: what your OK button calls before saving. What the user is still typing is committed first. Returns the count of properties in error (0 = the grid is valid, nothing moves), -2 when the component is not created
of_values ( ) → stringEvery value at once, read live — what the user is still typing is committed first, as by AcceptText (ue_value_changing when the veto is on, then ue_value_changed) —, as a JSON object: the top properties, then each category as an object of its own, each group as an object inside what holds it — hidden ones included. Typed: a number is a number, written digit for digit (a decimal of 21 digits stays exact), 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("page/margin"), lnv_json.of_get_number("appearance/font/size")
of_reset ( ) → longEmpties the grid (of_clear) then puts every property of the component back to its default: categorized view, toolbar and help pane shown, labels at 40%, empty search, question off. Returns 0 once applied, -2 when the component is not created

On a category and on a property #

A category holds properties; a group (TYPE_GROUP) holds its sub-properties; a list property (TYPE_ENUM) holds its options, named by their value. The three methods walk them.

MethodPurpose
of_count ( ) → longNumber of properties of the category, hidden ones included; number of sub-properties of a group; number of options of a list property (0 for the other types)
of_keys_at (long al_index) → stringAddress of the property at rank al_index (from 1) — "page/margin", "appearance/font/size" on a group, ready to go back into of_property; on a list property, the address followed by the value of the option. "" out of range
of_has (string as_key) → booleanDoes the category or the group hold this property? Does the list property offer this value?

Events #

EventRaised when
ue_value_changed (string as_keys, string as_value)The user changed a value (an entry confirmed by Enter or by leaving the cell, a check box ticked by a click, Space or Enter / F2, a choice in a list, a colour, a date, a double click on a label). as_keys is the address of the property, as_value its new value as text; it is already written. A value your code writes (is_value) raises nothing, like SetItem: your code knows what it wrote. Delete or the Reset button (back to the default) and Up / Down in a number are gestures of the user: they raise it too
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 cell shows the old value again, which of_property(as_keys).is_value still returns during the event. Return TRUE to accept. Set of_property(as_keys).is_error before refusing: the user reads why
ue_selection_changed (string as_from_keys, string as_keys)The selection has moved: a click, the keyboard, or your code (of_select_property, of_clear_selection — an order raises it too, like SelectItem), or the selected row left: folded into its category or its group (as_keys is then that header or that group), removed, or no longer drawn — hidden, hidden by the search (even by a fold made during it), beyond the demo cap (as_keys is then ""). A row is a property ("page/margin") or the header of a category ("page"); as_from_keys is the one that was left
ue_category_toggled (string as_key, boolean ab_collapsed)A category folded (ab_collapsed = TRUE) or unfolded: a click on its header, the keyboard, its ib_collapsed set by your code, of_expand_all, of_collapse_all, or of_select_property on a property it holds
ue_property_toggled (string as_keys, boolean ab_expanded)An expandable property (TYPE_GROUP) opened (ab_expanded = TRUE) or closed: a click on its chevron, a double click on its label, the keyboard, its ib_expanded set by your code, of_expand_all, of_collapse_all, or of_select_property on what it holds. as_keys is the address of the group
ue_ellipsis_clicked (string as_keys)The … button of a property (ib_ellipsis) was clicked, or Ctrl+Enter was pressed on its row: open your dialog, then write the result into of_property(as_keys).is_value. The button stays active on a read-only property: of_property(as_keys).ib_readonly tells whether to open your dialog read-only
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
Up / Down arrows, Home / End, Page Up / Page DownMove the selection from row to row; Page Up / Page Down move by the height of the list
Left / Right arrowsOn a header, fold or unfold the category; on a group, Right opens it and Left closes it; Right on a header or a group already open goes down to its first row; on a property, Left goes up to what holds it, its group or its header (reversed right to left). During a search, folding and unfolding do nothing, by the keyboard or the mouse
Enter, F2Go into the editor of the property (open the list, the colour picker or the calendar); a boolean flips; on a header or a group, fold or unfold. With nothing to do (no row, a read-only property), the key is left to the window: its default button
SpaceChecks or unchecks a boolean; on a header or a group, folds or unfolds
Enter / Escape in an editorConfirms the entry / gives the old value back; the keyboard returns to the list. Escape in an untouched field is left to the window: its Cancel button
Ctrl+EnterThe … button of the selected row (ue_ellipsis_clicked)
TabThe list of properties is one stop of the Tab key, whatever its size: among the editors, only those of the selected row take Tab; the list that has the keyboard with no row selected shows an outline
Double click on a labelA boolean flips, a list moves to the next choice, a group opens or closes
DeleteOn the selected row: puts the default value back (is_default) or, without a default, empties the value — a text, a number, a date, a colour, a list; a boolean without a default stays. The Reset button (↺) of the toolbar puts the default back as well. A gesture of the user: ue_value_changed
Backspace, a character keyOn the selected row of a text, a number or a password: start the entry — Backspace opens the editor, the text kept and the caret at its end (it no longer empties the value); a character key opens the editor with that character, as in the property window of Visual Studio. On a list, a colour, a date or a check box: nothing
F4, Alt+DownOpen the list, the colour picker or the calendar of the selected row, from the list or from its button, like a drop-down list of Windows
Up / Down in a numberAdd / take id_step, within is_min / is_max; each step raises ue_value_changed
Splitter: Left / Right, Home / EndThe line between labels and values takes the keyboard (Tab): Left / Right move it by 5% (reversed right to left), Home / End take it to the ends (ii_label_width)
Dragging the line between labels and valuesChanges ii_label_width
Search box: Escape / Down arrow or EnterClears the search / goes into the list, on the first row when none is selected

Examples #

The settings of a window #

// The whole grid in one paint
uo_grid.of_set_redraw(/*on*/ false)

// At the top, without a category : the name of the window
uo_grid.of_add_property(/*keys*/ "name", /*label*/ "Name", /*type*/ u_pbt_propertygrid.TYPE_STRING, /*value*/ "Customers")

// A value given by the application : shown, not editable
uo_grid.of_add_category(/*key*/ "identity", /*title*/ "Identity")
uo_grid.of_add_property(/*keys*/ "identity/id", /*label*/ "Id", /*type*/ u_pbt_propertygrid.TYPE_STRING, /*value*/ "W-0042")
uo_grid.of_property(/*keys*/ "identity/id").ib_readonly = true
uo_grid.of_add_property(/*keys*/ "identity/created", /*label*/ "Created on", /*type*/ u_pbt_propertygrid.TYPE_DATE, /*value*/ String(Today(), "yyyy-mm-dd"))

// A number, a check box and a list of choices
uo_grid.of_add_category(/*key*/ "layout", /*title*/ "Layout")
uo_grid.of_add_property(/*keys*/ "layout/width", /*label*/ "Width", /*type*/ u_pbt_propertygrid.TYPE_NUMBER, /*value*/ "1200")
uo_grid.of_add_property(/*keys*/ "layout/resizable", /*label*/ "Resizable", /*type*/ u_pbt_propertygrid.TYPE_BOOLEAN, /*value*/ "true")
uo_grid.of_add_property(/*keys*/ "layout/state", /*label*/ "Window state", /*type*/ u_pbt_propertygrid.TYPE_ENUM, /*value*/ "normal")
uo_grid.of_add_option(/*keys*/ "layout/state", /*value*/ "normal", /*text*/ "Normal")
uo_grid.of_add_option(/*keys*/ "layout/state", /*value*/ "maximized", /*text*/ "Maximized")

// A colour, as its RGB long written out
uo_grid.of_add_category(/*key*/ "appearance", /*title*/ "Appearance")
uo_grid.of_add_property(/*keys*/ "appearance/back_color", /*label*/ "Back colour", /*type*/ u_pbt_propertygrid.TYPE_COLOR, /*value*/ String(RGB(/*red*/ 240, /*green*/ 244, /*blue*/ 250)))

// One single paint, then the first value to edit
uo_grid.of_set_redraw(/*on*/ true)
uo_grid.of_select_property(/*keys*/ "layout/width")

An inspector that applies every change #

// event ue_value_changed of uo_grid : (string as_keys, string as_value)
// The address says which setting of the window the user changed
choose case as_keys
    case "name"
        w_customers.title = as_value
    case "layout/width"
        w_customers.width = Long(as_value)
    case "layout/resizable"
        w_customers.resizable = (as_value = "true")
    case "appearance/back_color"
        w_customers.backcolor = Long(as_value)
end choose

The … button: a dialog of your own #

// The icon : typed by hand, or chosen through the "..." button
uo_grid.of_add_property(/*keys*/ "appearance/icon", /*label*/ "Icon", /*type*/ u_pbt_propertygrid.TYPE_STRING, /*value*/ "img\customers.png")
uo_grid.of_property(/*keys*/ "appearance/icon").ib_ellipsis = true
// event ue_ellipsis_clicked of uo_grid : (string as_keys)
// Local variables
string ls_path, ls_file
constant unsignedlong OFN_EXPLORER = 2
constant unsignedlong OFN_NOCHANGEDIR = 64

// The application opens its own dialog - in the Explorer style, without moving the current folder of the application - then writes the result (this raises nothing)
if GetFileOpenName("Icon", ls_path, ls_file, "png", "Pictures (*.png),*.png", /*initdir*/ "", OFN_EXPLORER + OFN_NOCHANGEDIR) = 1 then
    uo_grid.of_property(/*keys*/ as_keys).is_value = ls_path
end if

Validating before writing #

// The question is OFF by default : turn it on to be able to refuse a value
uo_grid.ib_veto_changes = true
// event ue_value_changing of uo_grid : (string as_keys, string as_value)
// FALSE refuses : the cell shows the old value again. is_error, set before, tells
// the user why : the help pane shows it until the value is edited again
choose case as_keys
    case "layout/width"
        if Long(as_value) < 200 or Long(as_value) > 4000 then
            uo_grid.of_property(/*keys*/ as_keys).is_error = "Between 200 and 4000 pixels."
            return false
        end if
    case "name"
        if Trim(as_value) = "" then
            uo_grid.of_property(/*keys*/ as_keys).is_error = "A window needs a name."
            return false
        end if
end choose

// Any other value is accepted
return true

Defaults, changes and Cancel #

Two separate notions, as in the property window of Visual Studio and in a DataWindow: the default value (is_default) says what is written in bold and what Delete puts back; the reference (of_load, of_reset_update) says what there is to save (of_is_dirty, of_dirty_keys) and what Cancel puts back (of_revert).

// The defaults of the window : a value that differs is written in bold
uo_grid.of_property(/*keys*/ "layout/width").is_default = "800"
uo_grid.of_property(/*keys*/ "layout/state").is_default = "normal"

// The width the user may type, and what Up / Down add or take
uo_grid.of_property(/*keys*/ "layout/width").is_min = "200"
uo_grid.of_property(/*keys*/ "layout/width").is_max = "4000"
uo_grid.of_property(/*keys*/ "layout/width").id_step = 50

// The saved settings, read back : they become the reference
uo_grid.of_load(/*json*/ is_saved_settings)
// OK : every value right first - otherwise the keyboard goes to the first error
if uo_grid.of_validate() > 0 then return

// Save only what changed (what the user is still typing counts too), then the screen becomes the reference
if uo_grid.of_is_dirty() then
    is_saved_settings = uo_grid.of_values()
    uo_grid.of_reset_update()
end if
// Cancel : every value back to the settings loaded
uo_grid.of_revert()

An expandable property #

A font fits in one closed row, which sums up its values, and opens on its three properties. Without a template the summary is Segoe UI; 9; False; is_summary writes it your way: Segoe UI, 9 pt, then , bold while Bold is checked.

// A group in the category appearance : the font, closed when it is added
uo_grid.of_add_category(/*key*/ "appearance", /*title*/ "Appearance")
uo_grid.of_add_property(/*keys*/ "appearance/font", /*label*/ "Font", /*type*/ u_pbt_propertygrid.TYPE_GROUP, /*value*/ "")

// What it holds is added at its address
uo_grid.of_add_property(/*keys*/ "appearance/font/name", /*label*/ "Name", /*type*/ u_pbt_propertygrid.TYPE_STRING, /*value*/ "Segoe UI")
uo_grid.of_add_property(/*keys*/ "appearance/font/size", /*label*/ "Size", /*type*/ u_pbt_propertygrid.TYPE_NUMBER, /*value*/ "9")
uo_grid.of_add_property(/*keys*/ "appearance/font/bold", /*label*/ "Bold", /*type*/ u_pbt_propertygrid.TYPE_BOOLEAN, /*value*/ "false")

// Its summary, written your way : "Segoe UI, 9 pt", then ", bold" while Bold is checked
uo_grid.of_property(/*keys*/ "appearance/font").is_summary = "{name}, {size} pt{?bold}, bold{/bold}"

// Open it : its three values show under it
uo_grid.of_property(/*keys*/ "appearance/font").ib_expanded = true
// Local variables
n_pbt_json lnv_json
double ld_size

// The group is an object in the values : the same address reads its size
lnv_json.of_load(/*json*/ uo_grid.of_values())
ld_size = lnv_json.of_get_number(/*path*/ "appearance/font/size")
// event ue_property_toggled of uo_grid : (string as_keys, boolean ab_expanded)
// The group that opened or closed, by its address
if ab_expanded then
    lb_log.AddItem(as_keys + " opened")
else
    lb_log.AddItem(as_keys + " closed")
end if

Reading every value at once #

// Local variables
n_pbt_json lnv_json
string ls_state

// Every value, typed, read along the same addresses as the grid
lnv_json.of_load(/*json*/ uo_grid.of_values())
w_customers.title = lnv_json.of_get_string(/*path*/ "name")
w_customers.width = lnv_json.of_get_number(/*path*/ "layout/width")
w_customers.resizable = lnv_json.of_get_boolean(/*path*/ "layout/resizable")

// One value alone : always text
ls_state = uo_grid.of_property(/*keys*/ "layout/state").is_value

Views, search and width of the labels #

// One flat list sorted by label, instead of the categories
uo_grid.is_sort = u_pbt_propertygrid.SORT_ALPHABETICAL

// Keep only what speaks of a colour
uo_grid.is_filter = "colour"

// The labels take half of the width, and the help pane goes away
uo_grid.ii_label_width = 50
uo_grid.ib_help = false

Walking a category #

// Local variables
n_pbt_propertygrid_category lnv_layout
string ls_keys
long ll_i

// Each property of the category, by its full address
lnv_layout = uo_grid.of_category(/*key*/ "layout")
for ll_i = 1 to lnv_layout.of_count()
    ls_keys = lnv_layout.of_keys_at(/*index*/ ll_i)
    lb_values.AddItem(ls_keys + " = " + uo_grid.of_property(/*keys*/ ls_keys).is_value)
next

Reloading a list of choices #

// The states offered depend on the kind of window : the old choices go,
// the property keeps its handle, its flags and its place
uo_grid.of_clear_options(/*keys*/ "layout/state")

// The new choices, then the value to show among them
uo_grid.of_add_option(/*keys*/ "layout/state", /*value*/ "docked", /*text*/ "Docked")
uo_grid.of_add_option(/*keys*/ "layout/state", /*value*/ "floating", /*text*/ "Floating")
uo_grid.of_property(/*keys*/ "layout/state").is_value = "docked"

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