PBToolboxAI v5 ← Site

treeview — u_pbt_treeview #

← Component reference · Guide contents

treeview shows data as a tree: nodes that hold others, opened and closed by their chevron, with a picture, a badge, a three-state check box and a single or multiple selection. As soon as it is given a column, it becomes a tree list (TreeList): the first column carries the tree, the others show the fields of each node, sorted by a click on their header. A branch can promise children it reads only when it opens; the user renames a node (F2) and drags it elsewhere, your application being able to refuse first; a search keeps only what matches. The tree is filled by hand or from a hierarchical DataStore (a row names its parent), and can write back into it what the user changes.

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


At a glance #

Userobjectu_pbt_treeview
Item classesn_pbt_treeview_node (of_node(keys)), n_pbt_treeview_column (of_column(key))
Used forA folder tree, an organisation chart, product categories, rights to check, a bill of materials with its quantities
PrincipleA node has a key unique under its parent; its address joins the keys of its levels with / ("europe/france")
Opt-in optionsib_checkboxes, ib_editable, ib_drag_drop, ib_veto_changes, ib_veto_selection, ib_veto_toggle, ib_single_expand, ib_parents_first, ib_toolbar, ib_show_lines, ib_write_back, ib_allow_drop
Demo mode limitThe first 8 nodes of each level drawn, under each parent, in the order of the model, and 200 nodes in the whole tree, the first ones added (a hidden node counts, lots and groups included; a total never counts what the cap hides) — see demo mode

Quick start #

// Two levels : a continent, then its countries (the parent is added first)
uo_tree.of_add_node(/*keys*/ "europe", /*text*/ "Europe")
uo_tree.of_add_node(/*keys*/ "europe/france", /*text*/ "France")
uo_tree.of_add_node(/*keys*/ "europe/italy", /*text*/ "Italy")

// Open the continent : its countries show
uo_tree.of_node(/*keys*/ "europe").ib_expanded = true
// event ue_node_opened of uo_tree : (string as_keys)
// A double click or Enter on a node : your own window shows it
OpenWithParm(w_country, as_keys)

One address per node #

A node is named by its address: the keys of its levels joined with /. "europe" is at the top, "europe/france" inside "europe" — the parent is added first. A key is unique under its parent: "europe/paris" and "usa/paris" are two nodes. An address already taken, an unknown parent, an empty level, a key that holds | or starts with __: the add returns -5 and of_get_last_error says why. of_insert_node puts a node among its siblings (a position counted from 1), of_move_node moves it with its subtree under another parent ("" = the top): its address changes, and the handles taken at the old address are freed at once (a handle taken afterwards at the new one stays valid). To walk the tree: of_count() and of_keys_at(n) for the top level, of_count(address) and of_keys_at(address, n) for the children of a node — which returns the whole address, reusable as it is.

// Local variables
long ll_i

// Paris first among the cities of France, then Italy moved under a new region
uo_tree.of_add_node(/*keys*/ "europe/france/lyon", /*text*/ "Lyon")
uo_tree.of_insert_node(/*keys*/ "europe/france/paris", /*text*/ "Paris", /*image*/ "", /*position*/ 1)
uo_tree.of_add_node(/*keys*/ "south", /*text*/ "Southern Europe")
uo_tree.of_move_node(/*keys*/ "europe/italy", /*to_parent_keys*/ "south", /*position*/ 1)

// The children of France, by their whole address
for ll_i = 1 to uo_tree.of_count(/*keys*/ "europe/france")
    lb_cities.AddItem(uo_tree.of_keys_at(/*keys*/ "europe/france", /*index*/ ll_i))
next

Opening, selecting, checking #

A node opens by its chevron, a double click, the keyboard or your code (ib_expanded of its handle, of_expand_all, of_collapse_all, of_expand_to_level, of_scroll_to_node, what of_select_node opens; of_clear_children closes): each one raises ue_node_toggled, then ue_layout_changed. The selection follows is_selection_mode: one node (SELECT_SINGLE, the default), several with Ctrl and Shift as in the Explorer (SELECT_MULTIPLE), or none (SELECT_NONE: the keyboard only moves a focus). of_select_node selects a node alone, opens what holds it and brings it on screen — a tree opens to select, like the TreeView of Windows, but it judges first: a node hidden or held by a hidden one, beyond the demo cap or left out by the search is refused (-5, of_get_last_error says why) and nothing opens. ue_selection_changed receives the selected addresses as a JSON array — whether a click, the keyboard or your code changed it, or a selected node left the screen (a branch closed, a node removed or hidden, a node the search hides). Closing a branch brings the focus back onto it. With ib_checkboxes, each node has a box; with ib_check_cascade (the default), a check of the user on a parent checks its subtree except a greyed node and its subtree, which stay as they are (the parent then shows mixed); a check of your code checks everything. During a search, the cascade takes the whole subtree, hidden nodes included. Under the cascade, a parent is the state of the children the tree can draw (neither hidden nor, in demo mode, beyond the cap): checked when they all are, mixed when some are. A check set by the user raises ue_node_checked; a check set by your code (ib_checked, of_check_all) is a value, silent like SetItem. The children received under a checked leaf take its check — all of them, the whole answer to ue_children_needed — until a check (user or code) or a node moved inside ends it. A check of the user never touches a node beyond the demo cap, nor, with ib_write_back, a hidden one. of_select_nodes(as_keys_json) selects several nodes at once in SELECT_MULTIPLE — the pendant of of_selected_keys — with one question to the vetoes and one ue_selection_changed; the focus goes to the last address named.

// Local variables
n_pbt_json lnv_json
string ls_keys[]
long ll_i

// A box before each node : checking a parent checks its subtree
uo_tree.ib_checkboxes = true
uo_tree.of_node(/*keys*/ "europe/france").ib_checked = true

// Every checked node, in the order of the tree
lnv_json.of_load(/*json*/ uo_tree.of_checked_keys())
for ll_i = 1 to lnv_json.of_get_array(/*path*/ "", /*values*/ ls_keys)
    lb_chosen.AddItem(ls_keys[ll_i])
next

Asking first: selection and opening #

A master-detail screen must not leave an unsaved record. With ib_veto_selection, ue_selection_changing receives the selection now and the one asked (JSON arrays of addresses) before any choice: a click, Ctrl or Shift + click, the keyboard, F3, of_select_node, of_clear_selection, of_search_next or of_search_prev. Return FALSE to refuse: nothing moves, not even the keyboard focus, and the method returns -4; ue_node_clicked is raised anyway, the click took place. What leaves the selection as a consequence — a branch closed, a node removed, hidden or hidden by the search — is not asked. In the same way, ib_veto_toggle asks ue_node_toggling before each opening or closing, by a gesture or by the code: a refused ib_expanded sends nothing (of_get_last_error says it), of_expand_all, of_collapse_all and of_expand_to_level apply the other nodes and return -4, of_select_node and of_scroll_to_node return -4 without opening anything. of_select_node asks the openings first (ue_node_toggling), then the selection: an application never acts on a selection accepted for an opening it then refused. What leaves the selection as a consequence cannot be refused: to keep a closing branch from leaving an unsaved record, pair ib_veto_selection with ib_veto_toggle and refuse closing the branch that holds it. ib_single_expand leaves one open branch under each parent: opening a node closes its open siblings, without asking; setting it closes nothing at once.

// A record shown beside the tree : ask before the selection leaves it, and before
// a branch closes (closing the branch that holds it would leave it)
uo_tree.ib_veto_selection = true
uo_tree.ib_veto_toggle = true
// event ue_selection_changing of uo_tree : (string as_from_keys_json, string as_keys_json)
// Unsaved changes in the detail : the user decides
if dw_detail.ModifiedCount() > 0 then
    if MessageBox("Unsaved changes", "Leave this record without saving?", Question!, YesNo!) = 2 then return false
end if
return true
// event ue_node_toggling of uo_tree : (string as_keys, boolean ab_expanding)
// Closing the branch that holds an unsaved record would leave it : refused
if not ab_expanding and dw_detail.ModifiedCount() > 0 then
    if Pos(uo_tree.of_selected_key() + "/", as_keys + "/") = 1 then return false
end if
return true

Branches loaded on demand #

A large tree — a disk, a catalogue, a table of thousands of rows — need not be read at once. ib_has_children = true on a node is a promise: its chevron shows although it holds nothing. When it opens — a click, the keyboard, your code, or of_select_node opening what holds a node — ue_children_needed is raised: your of_add_node made during the event are there when the branch opens, after the line that opened it has returned (ib_expanded = true does not wait for them). The promise holds until that answer, even if a node was added or dropped inside meanwhile; add nothing, and it is withdrawn (the chevron goes). Set ib_has_children = true again and the next opening asks again. An opening made during the event raises one ue_node_toggled only. of_expand_all only opens the branches that already have children: a branch whose promise has not been answered stays closed, no question is asked in bulk. For a table, there is no need to call of_add_node row by row: of_from_datastore(ads, as_parent_keys) makes the rows of a DataStore the children of the node, in one message — the ItemPopulate → Retrieve pattern of the PowerBuilder TreeView. ROLE_PARENT there names a row of the same lot (empty or unknown: right under the node); the same DataStore given again under the same node replaces its lot, another DataStore adds its own. of_row of a node of a lot returns its row in its DataStore, and ib_write_back writes there. of_clear_children removes the children of a node and makes it promise again: its next opening asks again (the "refresh" of a folder). Give each lot its own DataStore (created when its branch opens, kept while its nodes are shown): a DataStore loaded again for another node — Reset, Retrieve, then of_from_datastore under another node — takes its rows to the new lot, and PowerBuilder gives the same RowIDs again after a Reset. The nodes of the earlier lot then keep what they show but have no row any more: of_row returns 0, a move by the code returns -5 ("loaded again"), a renamed text or a check is refused and ue_write_back_failed says why, and the load that caused it counts them (stale=N) in of_get_last_error. The same DataStore given again under the same node replaces its lot; to refresh a lot from another DataStore object, call of_clear_children first, or it is added beside the first one. A column named has_children (ROLE_HAS_CHILDREN) makes a row promise its own children, with no loop. as_parent_keys empty is refused (-5): the top of the tree is of_from_datastore(ads).

// A folder that promises children : nothing is read before it opens
uo_tree.of_add_node(/*keys*/ "docs", /*text*/ "Documents", /*image*/ "img\packimages.dll:png/folder")
uo_tree.of_node(/*keys*/ "docs").is_expanded_image = "mono:img\packimages.dll:svg/samples/folder-open"
uo_tree.of_node(/*keys*/ "docs").ib_has_children = true
// event ue_children_needed of uo_tree : (string as_keys)
// Local variables
datastore lds_folders
n_pbt_utils lnv_utils

// The folder opens : ITS sub-folders are retrieved into a DataStore of their own and
// added in ONE call - code is their key and name their text (of_map, once), the
// column has_children says whether each one promises its own
lds_folders = create datastore
lds_folders.DataObject = "d_folders"
lds_folders.SetTransObject(SQLCA)

// as_keys is the whole ADDRESS (docs/reports) : the code of the folder is its LAST key
lds_folders.Retrieve(lnv_utils.of_leaf(/*keys*/ as_keys))
uo_tree.of_from_datastore(/*ads*/ lds_folders, /*parent_keys*/ as_keys)

// One DataStore per lot, kept while its nodes are shown : of_row and ib_write_back
// read it ; the window destroys them when it closes
ids_lots[UpperBound(ids_lots) + 1] = lds_folders

Renaming and moving #

With ib_editable, the user renames a node: F2, or a click on the node already selected (after a short delay, like the Explorer). Enter (or leaving the field) keeps the text, Escape gives it back, an emptied text is refused (the edit is cancelled); ue_node_edited receives the text typed and the old one — a typed text is text, never markup. A command of your code during the edit keeps the field and what is typed in it; if the row being edited leaves the screen without a gesture of the user (code, scrolling), the edit is cancelled. With ib_drag_drop, they drag a node: on the top quarter of a row it goes before it, on the bottom quarter after it — as the first child when it is an open branch —, in the middle inside it; a closed branch held under the pointer opens after a moment, the tree scrolls near its edges, Escape cancels, a node never goes into its own subtree nor where its key is taken, and nothing is dropped on the row of a greyed node. A drag whose released button the tree cannot see — the window loses the focus (Alt+Tab, a message box) — is cancelled: nothing moves. Under a sort, or with ib_parents_first, a drop only goes inside. ue_node_moved receives the new address, the old one and the place under the new parent; a node dropped where it already is asks, writes and raises nothing. With ib_veto_changes, ue_node_editing and ue_node_moving are asked before — ue_node_moving with the final place, the one ue_node_moved will say: return FALSE to refuse — nothing changes, and of_move_node returns -4. One node is dragged at a time, even in a multiple selection.

// Renaming and moving by the user, with a question before each change
uo_tree.ib_editable = true
uo_tree.ib_drag_drop = true
uo_tree.ib_veto_changes = true
// event ue_node_moving of uo_tree : (string as_keys, string as_to_parent_keys, long al_position)
// Nothing goes to the top level : FALSE keeps the node where it is
if as_to_parent_keys = "" then return false
return true

Your code opens a name for editing too: of_edit_node(as_keys) — the EditLabel of a PowerBuilder TreeView, for a “New folder” that opens ready to be named. The node is selected first (what holds it opens), then the field opens; the end is the user's, as above. A lock stops the user, never your code: it works with ib_editable = false, on a read-only or a greyed node. of_cancel_edit() closes the field and gives the text back, like Escape.

// A new folder, opened at once in edit mode : the user types its name, then Enter
uo_tree.of_add_node(/*keys*/ "docs/new", /*text*/ "New folder")
uo_tree.of_edit_node(/*keys*/ "docs/new")

Locks, Delete and copy #

Three locks per node stop the user, never your code: ib_read_only (neither renamed nor dragged, it stays a target), ib_drop_target = false (nothing is dropped into it, neither a node nor a file — before or after it stays allowed) and ib_show_checkbox = false (no box: the node does not count for its parent, is not in of_checked_keys, and the cascade passes through it to its children). Delete raises ue_delete_requested with the selected nodes (in SELECT_NONE, the focused one; nothing selected otherwise: nothing is raised), except greyed and read-only ones: the tree removes nothing, your application calls of_remove_node for what it deletes. Ctrl+C (or Ctrl+Insert) raises ue_copy with the selected nodes (in SELECT_NONE, the focused one) as tab-separated text, a number raw (a dot, no grouping nor symbol): the text after as many tabs as its depth, then the columns of a tree list aligned, a header line first; the tree also tries the clipboard of the browser, which the page may be refused — ClipBoard(as_tsv) to be certain.

// The system folder : neither renamed nor dragged, nothing dropped into it, no check box
uo_tree.of_node(/*keys*/ "system").ib_read_only = true
uo_tree.of_node(/*keys*/ "system").ib_drop_target = false
uo_tree.of_node(/*keys*/ "system").ib_show_checkbox = false
// event ue_delete_requested of uo_tree : (string as_keys_json)
// Local variables
n_pbt_json lnv_json
string ls_keys[]
long ll_i

// Delete pressed : the tree removes nothing, your application decides
lnv_json.of_load(/*json*/ as_keys_json)
for ll_i = 1 to lnv_json.of_get_array(/*path*/ "", /*values*/ ls_keys)
    uo_tree.of_remove_node(/*keys*/ ls_keys[ll_i])
next
// event ue_copy of uo_tree : (string as_tsv)
// Ctrl+C : the nodes copied, ready for a spreadsheet
ClipBoard(as_tsv)

Files dropped from the Explorer #

With ib_allow_drop, the tree accepts files dragged from the Windows Explorer onto a node: the node under the cursor lights up during the drag, ue_drop_files receives its address and the full paths. as_keys is empty for a drop beside the nodes, on a greyed node or on a node whose ib_drop_target is false. ue_drag_enter and ue_drag_leave tell that a drag of files came in, then left without dropping.

// Files of the Explorer dropped on a folder of the tree
uo_tree.ib_allow_drop = true
// event ue_drop_files of uo_tree : (string as_keys, string as_files[])
// The folder under the cursor : your application copies the files there
if as_keys <> "" then wf_copy_files(as_files, uo_tree.of_get_field(/*keys*/ as_keys, /*field*/ "path"))

Columns, sort and layout #

of_add_column makes the tree a tree list: the first column carries the tree (its title is is_tree_title), each column added shows the field of the same key of each node — set by of_set_field, or a column of the DataStore. The handle of a column (of_column) sets its title, its width (0 = automatic), its alignment, its format (a numeric mask such as "#,##0.00" with the separators of the display language, or date tokens for a "yyyy-mm-dd" value, those of the datagrid), its visibility and its place. A click on a header sorts the siblings of every level — ascending, descending, then the order of addition — and raises ue_sort_changed; of_sort does the same from the code. The sort only changes the order shown: of_keys_at and the positions keep the order of addition. The user widens a column by the edge of its header and moves it by dragging the header. What they arranged — widths, order, visibility, sort, open nodes — is read by of_get_layout and given back by of_set_layout; ue_layout_changed carries the whole layout after every gesture that changes it, and when your code writes the width, the place or the visibility of a column (ii_width, ii_index, ib_visible) and the layout changes. Equal values keep the order of the model, and the sort is made again when the display language changes. Removing the sorted column (of_remove_column, of_clear_columns) raises ue_sort_changed with the next key of the sort — SORT_NONE when it was the only one —, then ue_layout_changed. The five colours of the handle of a column (il_accent, il_back_color, il_text_color, il_back_color_hover, il_text_color_hover) colour its header. As soon as there are columns, the tree column stays frozen when the list scrolls sideways. The sort can bear on several keys: of_sort(as_sort) reads the syntax of a DataWindow's SetSort ("type A, text D", "text" being the text of the nodes), of_sort(as_column, as_dir) also takes a field no column shows, and ue_sort_changed receives the whole sort as its third argument. ib_parents_first puts at every level the nodes that have (or promise) children before the leaves — the folders before the files — whatever the sort. A column cannot take the key text, which names the text of the nodes (-5): of_sort("text", as_dir) sorts by it, and of_sort accepts several blanks between a column and its direction, as SetSort does.

// A tree list : the population of each country beside the tree
uo_tree.is_tree_title = "Country"
uo_tree.of_add_column(/*key*/ "population", /*title*/ "Population", /*width*/ 120)
uo_tree.of_column(/*key*/ "population").is_format = "#,##0"
uo_tree.of_column(/*key*/ "population").is_align = u_pbt_treeview.ALIGN_END
uo_tree.of_set_field(/*keys*/ "europe/france", /*field*/ "population", /*value*/ "68000000")

// The largest countries first, inside each continent
uo_tree.of_sort(/*column*/ "population", /*dir*/ u_pbt_treeview.SORT_DESC)
// The folders first, then by type and by name
uo_tree.ib_parents_first = true
uo_tree.of_sort(/*sort*/ "type A, text A")

Exporting the tree with its hierarchy: of_export_csv(as_path) writes a CSV (UTF-8, semicolon) where the text of a node stands in the cell of its level — one cell to the right per level —, the columns of a tree list after the deepest level, a header line first; of_export_xlsx(as_path) writes an Excel workbook where the text is indented by its level and every branch grouped under its parent, foldable in Excel, numbers staying numbers. is_export_scope says which nodes: EXPORT_ALL (the default) every node, closed branches included, EXPORT_SHOWN the rows on the screen. The tree builds the file, the DLL writes it, ue_csv_saved / ue_xlsx_saved confirm; refused in demo mode.

// The tree written to Excel - every node, the closed branches too ; ue_xlsx_saved confirms
uo_tree.is_export_scope = u_pbt_treeview.EXPORT_ALL
uo_tree.of_export_xlsx(/*path*/ "C:\exports\countries.xlsx")

Branch totals #

A column of a tree list can show, on every branch, the total of what it holds: is_summary on its handle is SUMMARY_SUM, SUMMARY_AVG, SUMMARY_MIN, SUMMARY_MAX or SUMMARY_COUNT (the values that are not empty). The computation goes down to the leaves — an average is that of the leaves, never an average of averages — and the total is written in bold, by is_format — a count always as a plain number. A branch that has its own value keeps it and brings it up, except with ib_summary_over_value on the column. Only the nodes the tree can draw count (never a hidden one); with ib_summary_filtered, on the component, only what the search shows. of_get_field returns the raw total (a number with a dot), a sort on this column uses the totals, and the totals follow every add, removal or drag. SUMMARY_MIN and SUMMARY_MAX also compare dates (yyyy-mm-dd, yyyy-mm-dd hh:mm:ss, hh:mm:ss), and a sum of whole numbers stays exact beyond 2^53.

// Each continent shows the sum of its countries, in bold
uo_tree.of_column(/*key*/ "population").is_summary = u_pbt_treeview.SUMMARY_SUM

// The total of Europe, read like a field
st_total.text = uo_tree.of_get_field(/*keys*/ "europe", /*field*/ "population")

Searching #

is_filter only shows the nodes whose text holds the search (case ignored), with the nodes that hold them, open, and the text found highlighted. "" shows everything. What holds a match shows open without changing ib_expanded, and the chevrons are inert while the search lasts. With ib_toolbar, a bar above the tree carries a search field (which writes is_filter: a read gives it at once), and the Expand all and Collapse all buttons. Escape in the field empties the search, Down arrow goes to the tree, Enter and F3 go to the next match (Shift: the previous one) while the field keeps the keyboard. A selected node the search hides leaves the selection, and ue_selection_changed says it; the node that holds it is not selected in its place. of_select_node and of_scroll_to_node refuse a node the search leaves out (-5). To go from match to match, of_search_next and of_search_prev select the next or the previous one after the node with the focus, round the list (F3 and Shift+F3 from the keyboard), and of_match_count says how many the search found. of_find_node(as_field, as_value, as_after_keys) finds without filtering: the address of the first node whose field ("" = its text) holds exactly the value, in the order the tree shows, closed branches included; give the address found back for the next one. In SELECT_NONE, of_search_next and of_search_prev move the focus there, as F3 does. of_find_node starts after the place of as_after_keys in the order shown — a hidden node too; an unknown one returns "" and of_get_last_error says it. of_find_nodes(as_field, as_value) gives every match at once, as a JSON array in the order shown.

The search ignores accents by default, like the sort: “montreal” finds “Montréal”, and the dotted capital I of Turkish counts as an I (“izmir” finds “İzmir”); ib_filter_accents = true makes them count. In multiple selection, Ctrl+A selects every node shown (one question with ib_veto_selection).

// A search field above the tree, and a first search from the code
uo_tree.ib_toolbar = true
uo_tree.is_filter = "par"
// Local variables
string ls_keys

// The first node whose code is FR, selected if there is one
ls_keys = uo_tree.of_find_node(/*field*/ "code", /*value*/ "FR", /*after_keys*/ "")
if ls_keys <> "" then uo_tree.of_select_node(/*keys*/ ls_keys)
// Every node whose status is late, selected at once (a multiple selection)
uo_tree.is_selection_mode = u_pbt_treeview.SELECT_MULTIPLE
uo_tree.of_select_nodes(/*keys_json*/ uo_tree.of_find_nodes(/*field*/ "status", /*value*/ "late"))

From a DataStore #

of_from_datastore makes one node of each row of a hierarchical table: the ROLE_KEY column gives its key (without it, the RowID of the row), the ROLE_PARENT column names the key of the node that holds it (empty, unknown, or a loop: at the top) — with a ROLE_KEY column, a parent value names a key, never a RowID. The other roles: the text (ROLE_TEXT, shown as text, never as markup), the picture, the check, the badge and whether it starts open. A column plays a role when it is named like it (key, parent, text…) or when of_map says so. Every column is a field (of_get_field) — the columns of the list are not created for you: of_add_column with the column name. Dates and numbers are read whatever the regional settings, in one form: a date yyyy-mm-dd, a date-time yyyy-mm-dd hh:mm:ss, a number with a dot and no useless zero (1234,50 becomes 1234.5) — a decimal column is formatted and sorted as a number. Two rows with one key: the second takes "key~RowID". What the load had to put right (rows whose parent is not there, loops broken, keys taken twice, keys that cannot be one) is said by of_get_last_error, the return staying 0. of_reload_row reads one row changed by your code again and updates its node in place; a row whose key or parent changed moves its node. The node of a row removed by of_remove_node stays out until the next of_from_datastore; of_reload_row of its row brings it back. With ib_write_back, a renamed text is written into the ROLE_TEXT column and a node that changes parent writes it into the ROLE_PARENT column (NULL at the top, whatever its type; a move among the same siblings writes nothing), before the event; a node of a row then goes neither under a node added by hand nor under a row without a key value — its parent column could not say it. A value the DataStore refuses, or a DataStore destroyed since the load (no DataStore is bound), puts the node back and raises ue_write_back_failed. Without ib_write_back, a drag only moves the node: its row keeps what the DataStore says, and the next reading of the rows (of_map, is_group_by, of_reload_row, a new load) puts the node back where its row says — its old handle is freed. The Update stays yours. The handle of a node reads the number of its row (of_row). of_from_datastore also reads the rows of a DataWindow or a DataWindowChild — the one the user sees beside it: the tree then writes into that one. With ib_write_back, a check of the user is also written into the ROLE_CHECKED column of every row it changed — the cascade included, and the branches above whose state turns checked or unchecked (a branch that becomes mixed keeps its value) —, spelled the way the column already writes it (1/0, Y/N, T/F, O/N, -1/0…; a column that shows only its no writes the usual yes: Y for N, 1 for 0 — O and -1 only when the column already holds one); a column holding values that are neither a yes nor a no is refused, with its reason. Every value is judged first and the write is all or nothing: a refusal gives the checks of the gesture back and raises ue_write_back_failed. of_row_keys returns the address of the node of a row of the main DataStore: remove the node before a DeleteRow. Every write-back is all or nothing — each value judged before one is written; a date and time keeps its time, a time column is written, read and grouped as hh:mm:ss. A move written by of_move_node is judged again after ue_node_moving, and what was written goes back if the node does not move. With ib_write_back, a node of a row whose DataStore has no ROLE_TEXT column cannot be renamed. A new load frees only the handles of what it replaces: the handle of a node added by hand, an inert handle or one taken at an address not created yet stay valid; of_from_datastore from a DataWindow checks that its data can be shared (-5 with the reason). What a reading of the rows had to put right is counted in of_get_last_error — including the nodes added by hand lifted to the top because their group is gone (lifted=N) and the lots that left with their node (lots=N); of_map and is_group_by report it for every source.

// The departments : each row names its parent by its id
ids_depts.Retrieve()
uo_tree.of_map(/*role*/ u_pbt_treeview.ROLE_KEY, /*column*/ "dept_id")
uo_tree.of_map(/*role*/ u_pbt_treeview.ROLE_PARENT, /*column*/ "parent_id")
uo_tree.of_map(/*role*/ u_pbt_treeview.ROLE_TEXT, /*column*/ "dept_name")
uo_tree.of_from_datastore(/*ads*/ ids_depts)

// Two columns of the DataStore shown beside the tree
uo_tree.of_add_column(/*key*/ "head_count", /*title*/ "Staff")
uo_tree.of_add_column(/*key*/ "budget", /*title*/ "Budget")
uo_tree.of_column(/*key*/ "budget").is_format = "#,##0"

// A renamed or moved department is written into its row before the event
uo_tree.ib_write_back = true
uo_tree.ib_editable = true
uo_tree.ib_drag_drop = true
// event ue_node_moved of uo_tree : (string as_keys, string as_old_keys, long al_position)
// parent_id is already written in the row : save it
if ids_depts.Update() = 1 then commit using sqlca;

A role resolves per DataStore: the column of_map(ads, as_role, as_column) gave this DataStore, else that of of_map(as_role, as_column) when this DataStore has it, else the column named like the role. A lot of employees under a tree of departments thus needs none of the columns of the departments. A table whose roots carry 0 in their parent column (a NOT NULL column) says so by is_root_parent_value = "0" ("0|-1" for two values): those rows stand at the top without counting as orphans, and a node moved to the top with ib_write_back writes 0 instead of NULL. The ROLE_STATE_IMAGE and ROLE_OVERLAY_IMAGE columns give the state picture and the overlay picture of a node, read like ROLE_IMAGE.

// event ue_children_needed of uo_tree : (string as_keys)
// Local variables
datastore lds_staff
n_pbt_utils lnv_utils

// The employees of the department that opens, in a DataStore of their own
lds_staff = create datastore
lds_staff.DataObject = "d_staff"
lds_staff.SetTransObject(SQLCA)
lds_staff.Retrieve(lnv_utils.of_leaf(/*keys*/ as_keys))

// THEIR key and THEIR text : the departments keep dept_id and dept_name
uo_tree.of_map(/*ads*/ lds_staff, /*role*/ u_pbt_treeview.ROLE_KEY, /*column*/ "emp_id")
uo_tree.of_map(/*ads*/ lds_staff, /*role*/ u_pbt_treeview.ROLE_TEXT, /*column*/ "emp_name")
uo_tree.of_from_datastore(/*ads*/ lds_staff, /*parent_keys*/ as_keys)

// Kept while its nodes are shown ; the window destroys them when it closes
ids_lots[UpperBound(ids_lots) + 1] = lds_staff
// The roots of the table carry 0 in parent_id (a NOT NULL column) : 0 names the top
uo_tree.is_root_parent_value = "0"
uo_tree.of_from_datastore(/*ads*/ ids_depts)

A tree grouped by columns #

The TreeView style of a DataWindow, without a DataWindow: is_group_by = "region|country|city" makes the rows of of_from_datastore the leaves of groups built on the values of those columns, one level per column (ROLE_PARENT plays no part, ROLE_KEY names the leaves). The key of a group is its value (/, | and % written %2F, %7C, %25), its text the value written with the format of the column of the same key, "(empty)" for an empty value; the handle of a group node reads of_group_column() and of_group_value(). Set after a load, it groups again; of_reload_row puts a row whose value changed into its new group. of_set_level_image gives a picture to every node of a level, of_expand_to_level opens the tree down to a level, and is_summary gives the totals of each group. With ib_write_back, a leaf dropped under a group of the last level writes all its group columns before the event; elsewhere, and for a group node, the move is refused. The text of a group follows a format set after the load, the arrival of its column and the display language; a value starting with __ is written %5F_ in the key; of_group_value of a date and time is yyyy-mm-dd hh:mm:ss, of a time hh:mm:ss. Without ib_write_back, a leaf dragged under another group goes back to its own at the next grouping, since its row did not change; a node added by hand under a group that disappears goes up to the top.

// The sales, one row per sale, grouped by region, country and city : the customer is
// the text of each sale
uo_tree.is_group_by = "region|country|city"
uo_tree.of_map(/*role*/ u_pbt_treeview.ROLE_TEXT, /*column*/ "customer")
uo_tree.of_from_datastore(/*ads*/ ids_sales)

// The amount of each sale, and the total of each group
uo_tree.of_add_column(/*key*/ "amount", /*title*/ "Amount")
uo_tree.of_column(/*key*/ "amount").is_format = "#,##0.00"
uo_tree.of_column(/*key*/ "amount").is_summary = u_pbt_treeview.SUMMARY_SUM

// A globe before every region ; only the regions show at first
uo_tree.of_set_level_image(/*level*/ 1, /*image*/ "mono:img\packimages.dll:svg/ribbon/globe", /*expanded_image*/ "")
uo_tree.of_expand_to_level(/*level*/ 1)

Properties #

PropertyTypeDefaultPurpose
is_selection_modestringSELECT_SINGLEHow the user selects: SELECT_SINGLE (one node), SELECT_MULTIPLE (Ctrl and Shift, like the Explorer) or SELECT_NONE (the keyboard only moves a focus, nothing is selected). Another value is refused: the old one stays, of_get_last_error says why
ib_checkboxesbooleanfalseA check box before each node
ib_check_cascadebooleantrueA check of the user on a node checks its subtree, except a greyed node and its subtree; a check of the code checks everything. A parent is the state of the children the tree can draw: checked when they all are, mixed (CHECK_MIXED) when some are. false: every node keeps its own check; a check of the user never touches a node beyond the demo cap, nor, with ib_write_back, a hidden one
ib_editablebooleanfalseThe user renames a node: F2, or a click on the node already selected. Enter keeps, Escape gives back (ue_node_edited); an emptied text is refused
ib_drag_dropbooleanfalseThe user drags a node before, after or inside another one (ue_node_moved); never into its own subtree; a drop on the bottom quarter of an open branch makes it its first child; the window losing the focus during a drag cancels it
ib_veto_changesbooleanfalseAsk ue_node_editing and ue_node_moving before a typed text is kept or a node moves — by a gesture as by of_move_node (off by default, like every cancelable event)
is_filterstring""The search: only the nodes whose text holds it show, with those that hold them, open, the text found highlighted. "" shows everything. The field of the toolbar writes it too: a read gives it at once; a selected node it hides leaves the selection (ue_selection_changed)
ib_toolbarbooleanfalseA bar above the tree: the search field, Expand all, Collapse all
ib_show_linesbooleanfalseThin lines join each node to its parent
ii_indentinteger18The indentation of a level, in pixels, from 8 to 64. Another value is refused: the old one stays, of_get_last_error says why
is_tree_titlestring""The title of the tree column of a tree list. Accepts rich text markup
ib_write_backbooleanfalseWith a tree fed by of_from_datastore: a renamed text is written into the ROLE_TEXT column, a node that changes parent writes it into the ROLE_PARENT column (NULL at the top; a move among the same siblings writes nothing), before the event. A node of a row then goes neither under a node added by hand nor under a row without a key value. A value refused, or a DataStore destroyed, puts the node back and raises ue_write_back_failed. Without it, a drag moves the node only. The Update stays yours. A check of the user is also written into the ROLE_CHECKED column of every row it changes. A check also writes the branches above whose state turns checked or unchecked; every value is judged first, all or nothing; with no ROLE_TEXT column a node of a row cannot be renamed
ib_enabledbooleantruefalse greys the tree out: the nodes stay shown, nothing answers the user any more and the tree leaves the Tab order. Your code still changes everything; it no longer takes the focus
ib_veto_selectionbooleanfalseAsk ue_selection_changing before the selection changes: a choice of the user, of_select_node, of_clear_selection (off by default, like every cancelable event)
ib_veto_togglebooleanfalseAsk ue_node_toggling before a node opens or closes, by a gesture or by the code
ib_single_expandbooleanfalseOne open branch under each parent: opening a node closes its open siblings (ue_node_toggled for each, never asked); setting it closes nothing at once; of_expand_all and of_expand_to_level ignore it
ib_parents_firstbooleanfalseAt every level, the nodes that have (or promise) children before the leaves — the folders before the files —, whatever the sort; a drop then only goes inside
ib_summary_filteredbooleanfalseThe totals of the branches (is_summary of a column) count only what the search shows; false: every node the tree may draw
is_group_bystring""A tree grouped by columns, like the TreeView style of a DataWindow: `"regioncountry" makes one level of groups per column, the rows of of_from_datastore being the leaves under the last one. Set after a load, it groups again; a column the DataStore has not is ignored. What a grouping had to put right, for every source, is read in of_get_last_error`
is_root_parent_valuestring""The parent values that name the top of the tree, joined by ` ("0", "0-1"): such a row stands at the top without counting as an orphan, and a node moved to the top with ib_write_back writes the first one instead of NULL. ""`: only an empty (NULL) parent names the top
ib_filter_accentsbooleanfalsefalse: the search (is_filter) ignores the accents, like the sort; true: they count
ib_auto_tooltipbooleantrueA label cut by the width shows its whole text in a tooltip — never over a tooltip your application gave the node
is_export_scopestringEXPORT_ALLWhat of_export_csv and of_export_xlsx write: EXPORT_ALL every node, closed branches included, EXPORT_SHOWN the rows on the screen. A hidden node is never written; another value is refused
ib_allow_dropbooleanfalseAccept files dropped from the Explorer onto a node: the node under the cursor lights up, ue_drop_files names it with the full paths
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 node — n_pbt_treeview_node #

Its handle, through of_node(address). A read asks the tree what the node is now. Its of_count() counts its children, its of_keys_at(n) returns the address of its n-th child (counted from 1), its of_has(key) says whether it has that child, its of_key() returns its key.

PropertyTypeDefaultPurpose
is_textstring""The text of the node. Accepts rich text markup. Empty, the node shows its key
is_imagestring""The picture before the text: a file of the workstation, a resource "file.dll:name", a glyph "mono:…" / "tint:…" drawn in the theme, an https:// address. http:// is refused (ue_image_failed, REASON_INSECURE)
is_expanded_imagestring""The picture while the node is open (an open folder); "" keeps is_image
is_state_imagestring""The state picture of the node, before its picture: a flag, a status — the StatePicture of a PowerBuilder TreeView. A click on it raises ue_state_clicked and does not select the node; same sources and refusals as is_image
is_overlay_imagestring""A small picture drawn over the bottom corner of the picture of the node (a shortcut arrow, a lock) — the OverlayPicture of a PowerBuilder TreeView
is_selected_imagestring""The picture of the node while it is selected — the SelectedPicture of a PowerBuilder TreeView. "" keeps is_image
ib_expandedbooleanfalsetrue opens the node (its children show), false closes it; ue_node_toggled follows, as for a click on its chevron. A node without children reads false; opening a node that only promises children asks for them (ue_children_needed); they arrive with the answer, after this line returns; with ib_veto_toggle, ue_node_toggling is asked first; refused, nothing is sent and of_get_last_error says it
ib_has_childrenbooleanfalseA promise of children: the chevron shows although nothing is added yet. Opening the node raises ue_children_needed, where your application adds them. The promise holds until the answer, even if a node was added or dropped inside; setting it true again asks again at the next opening
ib_checkedbooleanfalseThe check of the node (ib_checkboxes of the tree). With ib_check_cascade, writing it checks the whole subtree (greyed nodes included), and it reads true when its children the tree can draw are all checked (of_check_state says mixed); children received under a checked leaf take its check. A check written by your code raises nothing
ib_enabledbooleantruefalse greys the node: the user can neither select, check, rename nor drag it, nothing is dropped on its row, the keyboard skips it, and a check of the user on a parent leaves it as it is, with its subtree. Your code still selects and checks it
ib_visiblebooleantruefalse takes the node and its subtree off the screen; it keeps its address
is_badgestring""A small badge after the text (markup accepted): a count of unread items; "" none
ib_read_onlybooleanfalsetrue: the user can neither rename nor drag it (it stays a target), and Delete leaves it out; your code still can
ib_drop_targetbooleantruefalse: nothing is dropped into it, neither a dragged node nor a file of the Explorer; before or after it stays allowed
ib_show_checkboxbooleantruefalse: no check box on this node (ib_checkboxes) — it does not count for its parent, is not in of_checked_keys, and the cascade passes through it to its children
of_keys ( ) → string——Returns the address of the node: its keys joined with /
of_level ( ) → long——Returns its depth: 1 for a node at the top, 2 for a node inside it… 0 when the tree does not hold it
of_position ( ) → long——Returns its place among the children of its parent, counted from 1, in the order of addition (a sort only changes the order shown); 0 when the tree does not hold it
of_parent_keys ( ) → string——Returns the address of its parent; "" for a node at the top
of_check_state ( ) → string——Returns the state of its box: CHECK_ON, CHECK_OFF or CHECK_MIXED (some of its children checked, with ib_check_cascade); "" when the tree does not hold it
of_row ( ) → long——Returns the row number today, in the DataStore of of_from_datastore, of the row the node comes from — whatever key it had to take, and after a sort or a filter of the DataStore. 0 for a node added by hand or whose row is gone; a node of a lot returns its row in its DataStore, a group node 0; 0 too when the DataStore of its lot was loaded again for another node
of_group_column ( ) → string——For a group node of is_group_by: the column its level groups on ("region"); "" for any other node
of_group_value ( ) → string——For a group node: the value of its column it stands for, as the rows hold it (a date yyyy-mm-dd, a number with a dot, "" for the group of empty values); "" for any other node; a date and time yyyy-mm-dd hh:mm:ss, a time hh:mm:ss
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 node: its selection (while the tree has the focus) and its checked box take it; the badge keeps the colour of the theme (-1 = the one of the component)
il_back_colorlong-1Background of the row of the node, as RGB(r,g,b) (-1 = what the theme gives). A selected row takes the colours of the selection
il_text_colorlong-1Text colour of the row of the node (-1 = what the theme gives)
il_back_color_hoverlong-1Background of the row of the node under the pointer (-1 = what the theme gives)
il_text_color_hoverlong-1Text colour of the row of the node under the pointer (-1 = what the theme gives)

Properties of a column — n_pbt_treeview_column #

Its handle, through of_column(key). A value written by your code is silent, like SetItem — except its layout: ii_width, ii_index and ib_visible written by your code raise ue_layout_changed when the layout changes, as a gesture does. A read returns what the user may have changed (width, place).

PropertyTypeDefaultPurpose
is_titlestring""The title in the header (markup accepted); "" shows the key
ii_widthinteger0The width in pixels: 0 is automatic (as wide as the title and the values drawn), else 40 to 2000. The user drags the edge of the header: a read gives it at once
is_alignstringALIGN_STARTWhere the values stand in their cells: ALIGN_START, ALIGN_CENTER, ALIGN_END — the reading start and end, which follow a right-to-left application. Another value is refused
is_formatstring""How a value is written: a numeric mask for a number ("#,##0.00", separators of the display language), the date tokens of a PowerBuilder display format for a date or a date-time ("dd/mm/yyyy", "d mmm yyyy hh:mm" — mm after an hour is the minutes —, AM/PM, s, f, literals "…", [shortdate], [longdate], [time]), the same as the datagrid; "" writes the value as it is
ib_visiblebooleantruefalse takes the column off the screen; it keeps its place and its key
ii_indexinteger—Its place among all the columns, counted from 1: writing it moves the column. The user drags a header too: a read gives it at once
ib_sortablebooleantrueA click on its header sorts the tree by this column. The header of the tree column (is_tree_title) always sorts, by the text of the nodes
is_summarystring""The total each branch shows in this column: SUMMARY_SUM, SUMMARY_AVG, SUMMARY_MIN, SUMMARY_MAX, SUMMARY_COUNT (the values that are not empty), of what its children bring down to the leaves, in bold; SUMMARY_NONE (""): none. of_get_field reads the total, a sort uses it; SUMMARY_COUNT is written as a plain number, never by is_format; SUMMARY_MIN and SUMMARY_MAX also compare dates
ib_summary_over_valuebooleanfalsefalse: a branch that has its own value in the column keeps it (and brings it up); true: the total takes its place
il_accentlong-1Accent of the header: its sort arrow and the keyboard ring take it (-1 = the one of the component)
il_back_colorlong-1Background of the header, as RGB(r,g,b) (-1 = what the theme gives)
il_text_colorlong-1Text colour of the header (-1 = what the theme gives)
il_back_color_hoverlong-1Background of the header under the pointer (-1 = what the theme gives)
il_text_color_hoverlong-1Text colour of the header under the pointer (-1 = what the theme gives)
is_tooltipstring""Simple tooltip shown when hovering the header of the column
is_super_tooltip_titlestring""Title of the rich tooltip of the header (takes precedence over is_tooltip)
is_super_tooltip_textstring""Text of the rich tooltip of the header (rich markup accepted)
is_super_tooltip_imagestring""Image of the rich tooltip of the header

Constants #

They live on u_pbt_treeview: u_pbt_treeview.CHECK_MIXED.

ConstantValueMeaning
SUMMARY_NONE""No total — the default
SUMMARY_SUM"sum"The sum
SUMMARY_AVG"avg"The average of the leaves
SUMMARY_MIN"min"The smallest value
SUMMARY_MAX"max"The largest value
SUMMARY_COUNT"count"The count of values that are not empty
SELECT_SINGLE"single"One node selected at a time — the default
SELECT_MULTIPLE"multiple"Several, with Ctrl and Shift
SELECT_NONE"none"None: only a focus moves
SORT_ASC"asc"Ascending
SORT_DESC"desc"Descending
SORT_NONE"none"No sort: the order of addition
CHECK_ON"on"Checked (with the cascade: the whole subtree)
CHECK_OFF"off"Not checked
CHECK_MIXED"mixed"Some of the children checked
ALIGN_START"start"At the reading start — the default
ALIGN_CENTER"center"In the centre
ALIGN_END"end"At the reading end (numbers)
ROLE_KEY"key"The key of the node (an id); without it, the RowID
ROLE_PARENT"parent"The key of the node that holds it; with a ROLE_KEY column, never a RowID
ROLE_TEXT"text"Its text (shown as text)
ROLE_IMAGE"image"Its picture
ROLE_CHECKED"checked"Checked when the value says yes (1, -1, true, y, yes, on, t, o; no: 0, false, n, no, off, f, case ignored); written back the way the column already writes it; under the cascade, read for a leaf only: a parent there is the state of its children
ROLE_BADGE"badge"Its badge
ROLE_EXPANDED"expanded"Open at the load when the value says yes
ROLE_HAS_CHILDREN"has_children"The row promises children when the value says yes: a chevron, and ue_children_needed when the node opens; read once, when the node is made
ROLE_STATE_IMAGE"state_image"Its state picture
ROLE_OVERLAY_IMAGE"overlay_image"Its overlay picture
EXPORT_ALL"all"Export: every node, closed branches included — the default
EXPORT_SHOWN"shown"Export: the rows on the screen
REASON_INSECURE"insecure"An http:// address
REASON_NOT_FOUND"notfound"A file not found
REASON_UNC"unc"A network path from a DataStore column, refused before any request

Methods #

MethodPurpose
of_add_node (string as_keys, string as_text)Adds a node at its address, at the end of its parent: "europe" at the top, "europe/france" inside "europe" (added first). as_text accepts markup ("" shows the key). Returns 0 once added, -5 for an address with an empty level, a key holding ` or starting with __, an address already taken, an unknown parent (of_get_last_error says which), -2` when the component is not created
of_add_node (string as_keys, string as_text, string as_image)The same with its picture (is_image of the handle). Returns 0 once added, -5 as above, -2 when the component is not created
of_insert_node (string as_keys, string as_text, string as_image, long al_position)Adds a node at a place among the children of its parent, counted from 1 (past the last: at the end). Returns 0 once added, -5 like of_add_node, -2 when the component is not created
of_remove_node (string as_keys)Removes a node and everything under it (their handles are freed). Removing the selected node moves the selection to what held it (ue_selection_changed). The node of a DataStore row stays out until the next of_from_datastore: neither of_map nor the reload of another row brings it back, of_reload_row of its row does. Returns 0 once removed, -5 for an address the tree does not hold, -2 when the component is not created
of_move_node (string as_keys, string as_to_parent_keys, long al_position)Moves a node with its subtree under another parent ("" = the top), at a place counted from 1 (past the last: at the end) — or to another place among its siblings. Its address changes: the handles taken at the old address are freed by this call, at once; a handle taken afterwards at the new one stays valid (a move among the same siblings frees nothing). With ib_veto_changes, ue_node_moving is asked first, with the final place; with ib_write_back, a node of a row that changes parent writes it before it moves (a move among the same siblings writes nothing). ue_node_moved follows, as for a drag. What it writes is judged again after ue_node_moving; if the node does not move, what was written goes back. Returns 0 (also when it is already there: nothing is asked, written nor raised), -4 when the application refuses or the DataStore refuses the value (ue_write_back_failed), -5 for an unknown node or parent, a move into its own subtree, a key already taken under the new parent, with ib_write_back a node of a row under a node added by hand or under a row without a key value, a node of a lot outside its lot or whose DataStore was loaded again for another node, a leaf of a grouped tree elsewhere than under a group of the last level, a group node, a node that moved or left during ue_node_moving, -2 when the component is not created
of_clear ( )Empties the tree: nodes, handles and rows read (the DataStore stays bound for a new of_from_datastore); the columns and the options stay. ue_selection_changed follows when nodes were selected. The columns keep their colours and tooltips. The pictures of the levels (of_set_level_image) stay too. Returns 0 once applied, -2 when the component is not created
of_node (string as_keys)The n_pbt_treeview_node handle of a node, by its address (created on first access): its text, picture, check, states, colours and tooltip; an impossible address (an empty level, `, __`) returns an inert handle, which reads and writes nothing
of_edit_node (string as_keys)Opens the text of a node for editing from your code — the EditLabel of a PowerBuilder TreeView. The node is selected first (in SELECT_NONE, only brought into view), then the field opens; the end is the user's (Enter: ue_node_editing, then ue_node_edited; Escape or of_cancel_edit gives the text back). Works with ib_editable = false, on a read-only or a greyed node. Returns 0 once the field is open, -5 for a node the tree does not hold or cannot draw, a greyed tree, a group node of is_group_by or a row whose DataStore has no ROLE_TEXT column under ib_write_back (of_get_last_error says which), -4 when ue_selection_changing (or ue_node_toggling) refused, -2 when the component is not created
of_cancel_edit ( )Closes the field of an edit in progress (opened by the user or by of_edit_node) and gives the text back, like Escape: nothing is raised. Returns 0 once applied (no edit in progress: 0, nothing changes), -2 when the component is not created
of_expand_all ( )Opens every node that holds children (a branch only promised stays closed: nothing is asked). ue_node_toggled follows for each one, then ue_layout_changed once. Returns 0 once applied, -4 when ue_node_toggling refused some, -2 when the component is not created
of_collapse_all ( )Closes every node. ue_node_toggled follows for each one that was open, then ue_layout_changed once. Returns 0 once applied, -4 when ue_node_toggling refused some, -2 when the component is not created
of_expand_all (string as_keys)Opens a node and every node under it that has children (the ExpandAll(handle) of PowerBuilder); "" = the whole tree. Returns 0 once applied, -4 when ue_node_toggling refused some, -5 for a node the tree does not hold, -2 when the component is not created
of_collapse_all (string as_keys)Closes a node and every node under it; "" = the whole tree. Returns 0 once applied, -4 when ue_node_toggling refused some, -5 for a node the tree does not hold, -2 when the component is not created
of_expand_to_level (long al_level)Shows the tree down to a level: the levels above al_level open, this level and the next ones close (1: only the top shows). A branch whose promise has not been answered stays closed. ue_node_toggled for each node changed, then ue_layout_changed. Returns 0 once applied, -4 when ue_node_toggling refused some, -5 for a level under 1, -2 when the component is not created
of_set_level_image (integer ai_level, string as_image, string as_expanded_image)The picture of every node of a level (1 = the top) that has none of its own — the SetLevelPictures of PowerBuilder, handy for a grouped tree: as_image, and as_expanded_image while the node is open ("" keeps as_image). Two "" take the pictures of the level away. Returns 0 once set, -5 for a level under 1, -2 when the component is not created
of_clear_children (string as_keys)Removes the children of a node (their handles are freed; a node of a row stays out until the next of_from_datastore) and keeps the node, which promises again: its next opening raises ue_children_needed — the "refresh" of a folder. Open, it closes (ue_node_toggled). It closes without asking ue_node_toggling, and ue_layout_changed follows. Returns 0 once applied, -5 for a node the tree does not hold, -2 when the component is not created
of_scroll_to_node (string as_keys)Shows a node: what holds it opens (ue_node_toggled for each, outermost first), then the tree scrolls until it is in sight. The selection does not change. A node the tree could not draw is refused before anything opens. Returns 0 once applied, -5 for a node the tree does not hold or does not draw (hidden or held by a hidden one, beyond the demo cap, left out by the search; of_get_last_error says which), -4 when ue_node_toggling refuses to open a node that holds it (ib_veto_toggle), -2 when the component is not created
of_select_node (string as_keys)Selects a node alone, like a click: what holds it opens (a promised branch asks its question), the focus goes to it and it comes into view; ue_selection_changed follows (already selected alone: nothing is raised). A greyed node is selected too: greyed stops the user, not your code. The node is judged first: a tree opens to select, like the TreeView of Windows, but nothing opens for a node refused. With both vetoes, the openings are asked first (ue_node_toggling), then the selection (ue_selection_changing). Returns 0 once selected, -5 for a node the tree does not hold or does not draw (hidden or held by a hidden one, beyond the demo cap, left out by the search; of_get_last_error says which), or when is_selection_mode is SELECT_NONE, -4 when ue_selection_changing refuses the selection (ib_veto_selection) or ue_node_toggling a node it opens (ib_veto_toggle), -2 when the component is not created
of_select_nodes (string as_keys_json)Selects several nodes at once — the pendant of of_selected_keys: as_keys_json is a JSON array of addresses, in any order (["europe/france","asia"], the same address twice counts once). Each node is judged first (there, and drawable once what holds it is open); then what holds them opens, the focus and the anchor go to the last address named, and ue_selection_changed follows once. With ib_veto_toggle, ue_node_toggling is asked for each node it opens, then with ib_veto_selection ue_selection_changing once for the whole selection. "[]" leaves nothing selected; in SELECT_SINGLE, one address at most. Returns 0 once selected (also when that selection is already there: nothing is raised), -4 refused, -5 for a text that is not a JSON array of addresses, an address the tree does not hold or does not draw (of_get_last_error says which), more than one address in SELECT_SINGLE, or SELECT_NONE, -2 when the component is not created
of_clear_selection ( )Leaves no node selected: ue_selection_changed("[]") follows (nothing selected already: nothing is raised). Returns 0 once applied, -4 when ue_selection_changing refuses, -2 when the component is not created
of_selected_key ( )The address of the selected node (the one with the focus when several are); "" otherwise. Read live
of_selected_keys ( )The addresses of every selected node, as a JSON array in the order of the tree: ["europe/france","asia"]; "[]" when none. Read live; read it with n_pbt_json
of_checked_keys ( )The addresses of every checked node, as a JSON array in the order of the tree. With ib_check_cascade, a node whose whole subtree is checked is one of them, a mixed node is not. Read live
of_check_all (boolean ab_checked)Checks (or unchecks) every node at once. A check written by the code is a value: nothing is raised. Returns 0 once applied, -2 when the component is not created
of_sort (string as_column, string as_dir)Sorts the nodes shown, at every level: by a column (its key) or by the text of the nodes (as_column = ""). as_dir: SORT_ASC, SORT_DESC or SORT_NONE (the order of addition). The order of the model (of_keys_at, of_position) does not change. ue_sort_changed then ue_layout_changed follow, as for a click on a header. as_column may also name a field no column shows; a summarised column sorts on its totals, and with ib_parents_first the branches stay before the leaves. as_column "text" is the text of the nodes, like "". Returns 0 once applied (also when the sort is already that one: nothing is raised), -5 for a name that is not one (letters, digits and _) or an unknown direction, -2 when the component is not created
of_sort (string as_sort)Sorts on several keys, in their order: the first one decides, the next ones break its ties. as_sort is written as a DataWindow's SetSort reads it — "type A, text D" (asc and desc too, no direction = ascending, text = the text of the nodes, any other name a column or a field); "" takes the sort away. ue_sort_changed then ue_layout_changed follow. Several blanks between a column and its direction are accepted, as by SetSort. Returns 0 once sorted (also when the sort is already that one: nothing is raised), -5 for a name that is not one, another direction or a key named twice, -2 when the component is not created
of_find_node (string as_field, string as_value, string as_after_keys)The address of the first node whose field holds exactly as_value (the case counts): as_field "" = the text of the node, any other name a field (yours, a column of the DataStore, the total of a summarised column). In the order the tree shows, closed branches and nodes the search leaves out included, never a hidden node; as_after_keys "" starts at the first node, else after that one: give the address found back for the next one. "" when there is none (of_get_last_error says why when the arguments were refused). It starts after the place of as_after_keys in the order shown — a hidden node too; an unknown as_after_keys, or a call during a wait, returns "" and of_get_last_error says why (this read empties it first). of_find_nodes returns them all at once
of_find_nodes (string as_field, string as_value)Every node whose field holds exactly as_value (the case counts), in one pass: their addresses as a JSON array, in the order the tree shows (["europe/france","asia"]); "[]" when none. as_field "" is the text of the node, any other name a field. Closed branches and nodes the search leaves out are looked through, never a hidden node (nor, in demo mode, a node beyond the cap). "[]" with of_get_last_error when the field name is not one or during a wait. Read it with n_pbt_json
of_search_next ( )Selects the next node the search found (is_filter), after the node with the focus, round the list; F3 does the same. Like of_select_node: ue_selection_changed follows, and with ib_veto_selection ue_selection_changing is asked first. In SELECT_NONE, it moves the focus there and returns 0; during a wait, it returns -5 and of_get_last_error says why. Returns 0 once selected, -4 refused, -5 when the search is off or found nothing, -2 when the component is not created
of_search_prev ( )Selects the previous node the search found, before the node with the focus, round the list; Shift+F3 does the same. In SELECT_NONE, it moves the focus there and returns 0; during a wait, it returns -5 and of_get_last_error says why. Returns 0 once selected, -4 refused, -5 when the search is off or found nothing, -2 when the component is not created
of_match_count ( )Returns how many nodes the search (is_filter) found, read live; 0 without a search
of_add_column (string as_key, string as_title)Adds a column at the end: the tree becomes a tree list, the first column carries the tree, this one shows the field of the same key of each node (of_set_field, or the column of the DataStore). Its width is automatic. The key text is refused (-5): it names the text of the nodes. Returns 0 once added, -5 for a key that cannot be one (empty, holding / or `, starting with __) or a column already there, -2` when the component is not created
of_add_column (string as_key, string as_title, integer ai_width)The same with a width in pixels (0 = automatic; 40 to 2000). Returns 0 once added, -5 as above, -2 when the component is not created
of_remove_column (string as_key)Removes a column (its handle is freed); the last one gone, the list is a tree again. Removing the sorted column raises ue_sort_changed with the next key of the sort — SORT_NONE when it was the only one —, then ue_layout_changed. Returns 0 once removed, -5 for a column the list does not hold, -2 when the component is not created
of_clear_columns ( )Removes every column (their handles are freed): the list is a tree again. Removing the sorted column raises ue_sort_changed with the next key of the sort — SORT_NONE when it was the only one —, then ue_layout_changed. Returns 0 once applied, -2 when the component is not created
of_column (string as_key)The n_pbt_treeview_column handle of a column (created on first access): its title, width, alignment, format, visibility, place and whether a click on its header sorts
of_set_field (string as_keys, string as_field, string as_value)Gives a node a field of your own: of_set_field("europe/france", "population", "68000000"); the column of key population shows it. A node of a DataStore has every column as a field already; a field of yours with the same name wins. A field name: letters, digits and _, not starting with a digit. Returns 0 once set, -5 for an unknown node or a name that is not one, -2 when the component is not created
of_get_field (string as_keys, string as_field)Reads a field of a node, live: one of yours (of_set_field), a column of the DataStore it came from, or a built-in one (key, keys, text, badge, level, position, count, image). "" when it has none; for a summarised column (is_summary), the raw total of the branch, a number with a dot; the built-in count counts the children the tree can draw
of_from_datastore (datastore ads)Feeds the tree from a DataStore: one row = one node (see above). The nodes of an earlier load leave (their handles are freed); the nodes added by hand at the top stay, their handles too. Two rows with one key: the second takes "key~RowID"; a key that cannot be one (holding / or `, or starting with __): the RowID — its children still find it by its value. With a ROLE_KEY column, a parent value names a key, never a RowID. Dates and numbers are read whatever the regional settings (a date yyyy-mm-dd, hh:mm:ss, a number with a dot). What the load had to put right (missing parents, loops, keys taken twice or impossible) is said by of_get_last_error, the return staying 0. With is_group_by, the rows are the leaves of groups (ROLE_PARENT plays no part). A new load takes away the lots hung under its rows and keeps those hung under a node added by hand. A new load frees only the handles of what it replaces (a node added by hand, an inert handle, an address not created yet keep theirs); a node of a row moved without ib_write_back comes back to the place its row says. The report of of_get_last_error also counts the nodes added by hand lifted to the top (lifted=N) and the lots that left with their node (lots=N). Returns 0 once loaded, -5 when the DataStore is not valid, has no column, or lacks a column of_map named (of_get_last_error names it), -2` when the component is not created
of_from_datastore (datawindow adw)The same from the rows of a DataWindow: the tree writes back into this DataWindow (ib_write_back), the one the user sees beside it. Its data is shared with a scratch DataStore: a DataWindow that cannot share it is refused (-5, with the reason). Returns 0 once loaded, -5 as above, -2 when the component is not created
of_from_datastore (datawindowchild adwc)The same from the rows of a DataWindowChild (a drop-down DataWindow, a nested report): the tree writes back into it. Returns 0 once loaded, -5 as above, -2 when the component is not created
of_from_datastore (datastore ads, string as_parent_keys)A lot: the rows of a DataStore become the children of the node as_parent_keys, the rest of the tree does not move — what ue_children_needed does in one call. A role resolves for each DataStore: the column of of_map(ads, …) for this one, else that of of_map when it has it, else the column named like the role; ROLE_PARENT names a row of the same lot (empty or unknown: the node itself). The same DataStore given again under the same node replaces its lot (its handles are freed), another one adds its own; of_row of a node of the lot is its row in this DataStore, and ib_write_back writes there. A key already taken under the node takes "key~RowID". One DataStore per lot: loaded again for another node (Reset, Retrieve — PowerBuilder gives the same RowIDs again), it takes its rows to the new lot, and the nodes of the earlier lot keep what they show without a row (of_row 0, a move by the code -5, a write-back refused with ue_write_back_failed; the load counts stale=N). To refresh a lot from another DataStore object, of_clear_children first. Returns 0 once loaded, -5 when the DataStore is not valid, has no column, lacks a column of_map(ads, …) named for it, or as_parent_keys is empty (the top of the tree is of_from_datastore(ads)) or is not a node of the tree, -2 when the component is not created
of_from_datastore (datawindow adw, string as_parent_keys)A lot from the rows of a DataWindow (see above). Returns 0 once loaded, -5 as above, -2 when the component is not created
of_from_datastore (datawindowchild adwc, string as_parent_keys)A lot from the rows of a DataWindowChild. Returns 0 once loaded, -5 as above, -2 when the component is not created
of_row_keys (long al_row)The address of the node of a row of the DataStore of of_from_datastore (the main one, not a lot), by its row number today: remove the node (of_remove_node) before the DeleteRow. "" when the row has no node (removed, never loaded, no such row). "" too once the main DataStore was given again as a lot, until the next main load
of_map (string as_role, string as_column)Gives a column of the DataStore a role (ROLE_KEY, ROLE_PARENT, ROLE_TEXT…) when its name does not say it: of_map(ROLE_PARENT, "manager_id"). The role holds for every DataStore that has that column: a lot read from a DataStore without it takes the column named like the role, and of_map(ads, …) gives a DataStore its own roles. Before or after of_from_datastore: after, the rows are read again, and what was set on the nodes stays. "" as the column gives the role back to the column named like it. A new ROLE_KEY or ROLE_PARENT gives new addresses: only the handles of the nodes whose address changed are freed, the others stay valid. The roles given stay for the next of_from_datastore calls, even after of_clear; only of_reset forgets them. Its reading of the rows reports what it had to put right, for every source, in of_get_last_error. Returns 0 once set, -5 when the role is not one of the ROLE_ constants or the column is not one of the DataStore of the last of_from_datastore, -2 when the component is not created
of_map (datastore ads, string as_role, string as_column)Gives a column a role for this DataStore only: the lot of departments and the lot of employees of one tree each name their key and their text. Comes before of_map(as_role, as_column) for this DataStore; "" gives the role back. Before or after of_from_datastore: after, the rows are read again, what was set on the nodes stays. Returns 0 once set, -5 when the DataStore is not valid, the role is not one of the ROLE_ constants or the DataStore has no such column, -2 when the component is not created
of_map (datawindow adw, string as_role, string as_column)The same for the rows of a DataWindow given to of_from_datastore. Returns 0 once set, -5 for an argument refused, -2 when the component is not created
of_map (datawindowchild adwc, string as_role, string as_column)The same for the rows of a DataWindowChild given to of_from_datastore. Returns 0 once set, -5 for an argument refused, -2 when the component is not created
of_reload_row (long al_row)Sends one row of the DataStore of of_from_datastore to the tree again, read now: after your code changed it (SetItem, an edit window). al_row is the row number today; the node follows the row in place: what was set on it (open, greyed, visible, fields, colours) stays, nothing is raised. A row whose key or parent changed moves its node (the handles of what moved are freed, the others stay valid); a row the tree does not hold yet (InsertRow) arrives too, at its rank among its siblings; the row of a node removed by of_remove_node comes back. Every value is written as the load writes it. Only the main DataStore is read, not a lot (give it again); in a grouped tree, a row whose group value changed goes to its new group. Once the main DataStore was given again as a lot, it returns -5 until the next main load; a row whose parent or group changes moves its node in place. Returns 0 once sent, -5 when no DataStore is bound, the tree was emptied by of_clear since the load, or the row does not exist, -2 when the component is not created
of_export_csv (string as_path)Writes the tree to a CSV file (UTF-8, semicolon) with its hierarchy: the text of a node in the cell of its level, the columns after the deepest level, a header line first; a number raw, with the decimal mark of the display language. is_export_scope says which nodes; ue_csv_saved confirms; a relative path is written in the folder the application started in. Refused in demo mode. Returns 0 once asked, -5 when the path is empty, -2 when the component is not created
of_export_xlsx (string as_path)Writes the tree to an Excel workbook (.xlsx): the text of a node in the first column, indented by its level and grouped under its parent (Excel's outline levels: every branch folds with the + / - of the margin, up to 7 levels, everything written open), the columns after it, numbers staying numbers (with the format of their column when it is a numeric mask). is_export_scope says which nodes; ue_xlsx_saved confirms. Refused in demo mode. Returns 0 once asked, -5 when the path is empty, -2 when the component is not created
of_get_layout ( )What the user arranged, as JSON: {"columns":[{"key":"","width":260,"visible":true},…],"sort":{"column":"","dir":"none"},"expanded":["europe"]} — the tree column first (key ""), then each column in its order with its width (0 = automatic) and visibility, the sort, the open nodes. To store (file, registry, database) then give back to of_set_layout. Read live. It also carries "sorts" (every key of the sort), "selected" (the selected nodes) and "top" (the node at the top of the view, never pixels)
of_set_layout (string as_layout_json)Puts back a layout read by of_get_layout or received by ue_layout_changed: the widths, order and visibility of the columns, the sort, and which nodes are open (those it does not name close). A column or a node it names that the tree does not hold is ignored. ue_layout_changed is raised once when something changed (no ue_node_toggled nor ue_sort_changed: it puts a state back). It also gives the selection back (ue_selection_changed when it changed, nothing is asked) and scrolls to the node at the top. An address it asks open in a branch not answered yet is remembered: it opens by itself when the answer of its branch brings it (ue_node_toggled, then ue_layout_changed); ue_selection_changed is raised once. The questions of ib_veto_selection and ib_veto_toggle are not asked: it puts back a state, typically at the start. A promised branch that already holds a node is not opened on half its content: it asks ue_children_needed first. Returns 0 once applied, -5 when the text is empty or is not a JSON object, -2 when the component is not created

Events #

EventRaised when
ue_node_clicked (string as_keys)A node was clicked (and selected, unless in SELECT_NONE). A click on the chevron or on the box does not raise it; a greyed node raises nothing
ue_node_opened (string as_keys)A double click on a node, or Enter: open your editor for it. A double click on a branch also opens or closes it (ue_node_toggled)
ue_node_rclicked (string as_keys, long al_x, long al_y)A right click on a node (selected first, unless ue_selection_changing refuses it or the mode is SELECT_NONE: act on as_keys, never on of_selected_key), or the context-menu key (or Shift + F10) on the node with the focus. al_x, al_y are screen pixels — the point of the click, or the bottom-left corner of the node for the menu key —, the ones a contextmenu takes: uo_menu.of_show(al_x, al_y); for a PowerBuilder menu, PopMenu(PointerX(), PointerY()) of your window
ue_node_toggled (string as_keys, boolean ab_expanded)A node opened (ab_expanded = true) or closed: its chevron, a double click, the keyboard, or your code (ib_expanded, of_expand_all, of_collapse_all, and what of_select_node or of_scroll_to_node open); an opening made during ue_children_needed raises it once only; of_expand_to_level, of_clear_children (which closes) and a branch of_set_layout remembered raise it too
ue_selection_changed (string as_keys_json)The selection changed: a click, the keyboard, your code (of_select_node, of_clear_selection), or selected nodes that left the screen (a branch closed, a node removed or hidden, a node the search hides — what holds it is not selected in its place). as_keys_json is the JSON array of the selected addresses, in the order of the tree: ["europe/france", "asia"]
ue_node_checked (string as_keys, boolean ab_checked)The user checked (ab_checked = true) or unchecked a node: its box, or Space. With ib_check_cascade, its whole subtree follows, already in place when the event comes. A check written by your code raises nothing. A check of the user skips a greyed node and its subtree
ue_children_needed (string as_keys)A branch promised by ib_has_children opens and does not hold its children yet: add them now (of_add_node); the branch opens with them when the event returns, after the line that opened it has returned. The promise holds until this answer, even if a node was added or dropped inside; under a checked leaf, every child added takes its check. Add nothing, and the promise is withdrawn (the chevron goes)
ue_node_editing (string as_keys, string as_text) → booleanCancelable, asked before a text typed by the user is kept, only when ib_veto_changes = true. Returning FALSE refuses: the node keeps its text. By default: TRUE
ue_node_edited (string as_keys, string as_text, string as_old_text)The user renamed a node (F2, or a slow second click with ib_editable). The node already shows as_text; as_old_text is the one it had. With ib_write_back, the DataStore is already written
ue_node_moving (string as_keys, string as_to_parent_keys, long al_position) → booleanCancelable, asked before a node moves (a drag or of_move_node), only when ib_veto_changes = true. as_to_parent_keys is "" for the top, al_position its final place there, counted from 1, bounded to the siblings — the one ue_node_moved will say. A node already where it would go asks nothing. Returning FALSE refuses: nothing moves, and of_move_node returns -4. An of_move_node of the same node made inside the event is not asked again: it applies, and the of_move_node that asked then returns -5; another node is asked as usual. By default: TRUE
ue_node_moved (string as_keys, string as_old_keys, long al_position)A node moved with its subtree: a drag of the user or of_move_node. as_keys is its new address, as_old_keys the one it had, al_position its place under its new parent (counted from 1). The handles taken at the old address are already freed (of_move_node frees them at once, a drag before this event); a handle taken at the new one stays valid
ue_sort_changed (string as_column, string as_dir, string as_sort)The sort changed: a click on a header or of_sort. as_column is its first key ("" for the text of the nodes), as_dir its direction (one of the SORT_ constants), as_sort the whole sort written as a DataWindow's SetSort reads it ("type A, text D", "" for none). Removing the sorted column raises it with the next key of the sort, SORT_NONE when it was the only one
ue_layout_changed (string as_layout_json)The layout changed: the width or order of a column, the sort, a node opened or closed, or of_set_layout. as_layout_json is the whole layout, as of_get_layout gives it. A width, a place or a visibility of a column written by your code raises it too, when the layout changes; its title, format, alignment, sort permission and total stay silent
ue_write_back_failed (string as_keys, string as_col, string as_value, string as_reason)ib_write_back: the DataStore refused a value (a type it cannot hold, a row deleted since the load), or there is no DataStore any more (as_reason = no DataStore is bound). The node went back (as_keys is the address it has again); the event of the gesture is not raised. as_reason also says when the lot of the node was loaded again for another node, or when the check column holds values that are neither a yes nor a no
ue_image_failed (string as_keys, string as_reason)The picture of a node could not be shown: as_reason is REASON_INSECURE (an http:// address), REASON_UNC (a network path from a DataStore column) or REASON_NOT_FOUND (file not found). Said once per node and picture
ue_selection_changing (string as_from_keys_json, string as_keys_json) → booleanCancelable, asked before the user chooses another selection (a click, the keyboard, F3) and before of_select_node, of_clear_selection, of_search_next / of_search_prev, only when ib_veto_selection = true. as_from_keys_json is the selection now, as_keys_json the one asked (JSON arrays of addresses). Returning FALSE refuses: nothing moves, not even the keyboard focus, and the method returns -4. What leaves the selection as a consequence (a branch closed, a removal, a hidden node, the search) is not asked. of_select_nodes asks once for the whole selection. To send the user elsewhere, select inside the event, then return FALSE: a selection made inside the event is not asked again. By default: TRUE
ue_node_toggling (string as_keys, boolean ab_expanding) → booleanCancelable, asked before a node opens (ab_expanding = true) or closes, only when ib_veto_toggle = true: a gesture (chevron, double click, keyboard, *, the toolbar, a branch held under a drag) and your code (ib_expanded, of_expand_all, of_collapse_all, of_expand_to_level — one question per node —, of_select_node and of_scroll_to_node for the nodes they open). Returning FALSE refuses: the node stays as it is, and a method returns -4. The siblings ib_single_expand closes are not asked. By default: TRUE. of_clear_children closes without asking
ue_delete_requested (string as_keys_json)The user pressed Delete: as_keys_json is the JSON array of the selected nodes (in SELECT_NONE, the focused one; nothing selected otherwise: nothing is raised), neither greyed nor read-only. The tree removes nothing: call of_remove_node for what your application deletes
ue_copy (string as_tsv)The user pressed Ctrl+C (or Ctrl+Insert): as_tsv holds the selected nodes (in SELECT_NONE, the focused one; nothing selected otherwise: nothing is raised) in the order of the tree, one per line, the text after as many tabs as its depth, then the columns of a tree list aligned (a header line first), a number raw (a dot, no grouping nor symbol). The tree also tries the clipboard of the browser, which a page may be refused: ClipBoard(as_tsv) to be certain
ue_drop_files (string as_keys, string as_files[])Files were dropped from the Explorer onto a node (ib_allow_drop): as_keys is the address of the node under the cursor — empty for a drop beside the nodes, on a greyed node or on a node whose ib_drop_target is false —, as_files the full paths
ue_state_clicked (string as_keys)The user clicked the state picture of a node (is_state_image): a flag, a status your application turns on and off. The node is not selected, as with its box; a greyed node raises nothing. Raised for a gesture only
ue_csv_saved (string as_path, boolean ab_ok, string as_error)The CSV asked by of_export_csv was written (ab_ok = true) — or not: as_error says why (the demo mode, a path that cannot be written). as_path is the path written, a relative one resolved
ue_xlsx_saved (string as_path, boolean ab_ok, string as_error)The workbook asked by of_export_xlsx was written (ab_ok = true) — or not: as_error says why
ue_drag_enter ( )A drag of files from the Explorer came into the component (ib_allow_drop)
ue_drag_leave ( )The drag of files left without dropping — a drop raises ue_drop_files alone
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 #


Examples #

Rights to check #

// The rights of a role : a module, then what it allows
uo_rights.ib_checkboxes = true
uo_rights.of_add_node(/*keys*/ "sales", /*text*/ "Sales")
uo_rights.of_add_node(/*keys*/ "sales/read", /*text*/ "Read orders")
uo_rights.of_add_node(/*keys*/ "sales/write", /*text*/ "Create and change orders")
uo_rights.of_add_node(/*keys*/ "sales/delete", /*text*/ "Delete orders")

// What the role already has ; Sales shows mixed
uo_rights.of_node(/*keys*/ "sales/read").ib_checked = true
uo_rights.of_node(/*keys*/ "sales").ib_expanded = true
// event ue_node_checked of uo_rights : (string as_keys, boolean ab_checked)
// Delete never without write : checking it checks write too
if as_keys = "sales/delete" and ab_checked then this.of_node(/*keys*/ "sales/write").ib_checked = true

Finding the layout again at the next start #

// event ue_layout_changed of uo_tree : (string as_layout_json)
// Widths, order, sort and open nodes, kept for the next start
// (the nodes need STABLE keys : a ROLE_KEY column, never the RowIDs a new Retrieve gives)
SetProfileString("app.ini", "tree", "layout", as_layout_json)
// Local variables
string ls_layout

// After the tree is filled : the layout of the last time, if there is one
ls_layout = ProfileString("app.ini", "tree", "layout", "")
if ls_layout <> "" then uo_tree.of_set_layout(/*layout_json*/ ls_layout)

A menu on a node #

// The entries of the menu, once : uo_menu is a contextmenu of the window, and
// is_menu_keys a string instance variable that keeps the node right-clicked
uo_menu.of_add_item(/*keys*/ "expand", /*text*/ "Expand")
uo_menu.of_add_item(/*keys*/ "delete", /*text*/ "Delete")
// event ue_node_rclicked of uo_tree : (string as_keys, long al_x, long al_y)
// The node clicked is kept : the selection may have stayed elsewhere (a veto that
// refused it, SELECT_NONE). The menu opens where the user clicked
is_menu_keys = as_keys
uo_menu.of_show(/*x*/ al_x, /*y*/ al_y)
// event ue_item_selected of uo_menu : (string as_keys)
// The entry chosen acts on the node right-clicked
choose case as_keys
    case "expand"
        uo_tree.of_node(/*keys*/ is_menu_keys).ib_expanded = true
    case "delete"
        uo_tree.of_remove_node(/*keys*/ is_menu_keys)
end choose

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