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 #
| Userobject | u_pbt_treeview |
| Item classes | n_pbt_treeview_node (of_node(keys)), n_pbt_treeview_column (of_column(key)) |
| Used for | A folder tree, an organisation chart, product categories, rights to check, a bill of materials with its quantities |
| Principle | A node has a key unique under its parent; its address joins the keys of its levels with / ("europe/france") |
| Opt-in options | ib_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 limit | The 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 #
| Property | Type | Default | Purpose | ||
|---|---|---|---|---|---|
is_selection_mode | string | SELECT_SINGLE | How 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_checkboxes | boolean | false | A check box before each node | ||
ib_check_cascade | boolean | true | A 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_editable | boolean | false | The 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_drop | boolean | false | The 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_changes | boolean | false | Ask 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_filter | string | "" | 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_toolbar | boolean | false | A bar above the tree: the search field, Expand all, Collapse all | ||
ib_show_lines | boolean | false | Thin lines join each node to its parent | ||
ii_indent | integer | 18 | The 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_title | string | "" | The title of the tree column of a tree list. Accepts rich text markup | ||
ib_write_back | boolean | false | With 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_enabled | boolean | true | false 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_selection | boolean | false | Ask 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_toggle | boolean | false | Ask ue_node_toggling before a node opens or closes, by a gesture or by the code | ||
ib_single_expand | boolean | false | One 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_first | boolean | false | At 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_filtered | boolean | false | The totals of the branches (is_summary of a column) count only what the search shows; false: every node the tree may draw | ||
is_group_by | string | "" | A tree grouped by columns, like the TreeView style of a DataWindow: `"region | country" 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_value | string | "" | 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_accents | boolean | false | false: the search (is_filter) ignores the accents, like the sort; true: they count | ||
ib_auto_tooltip | boolean | true | A label cut by the width shows its whole text in a tooltip — never over a tooltip your application gave the node | ||
is_export_scope | string | EXPORT_ALL | What 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_drop | boolean | false | Accept 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_style | string | "" | Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change | ||
is_theme_mode | string | "" | Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change | ||
il_theme_accent | long | -1 | Accent colour of this component (-1 = the application accent, or the theme's) | ||
is_tooltip | string | "" | Simple tooltip shown when hovering the component | ||
is_super_tooltip_title | string | "" | Title of the rich tooltip (takes precedence over is_tooltip) | ||
is_super_tooltip_text | string | "" | Text of the rich tooltip (rich markup accepted) | ||
is_super_tooltip_image | string | "" | 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.
| Property | Type | Default | Purpose |
|---|---|---|---|
is_text | string | "" | The text of the node. Accepts rich text markup. Empty, the node shows its key |
is_image | string | "" | 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_image | string | "" | The picture while the node is open (an open folder); "" keeps is_image |
is_state_image | string | "" | 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_image | string | "" | 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_image | string | "" | The picture of the node while it is selected — the SelectedPicture of a PowerBuilder TreeView. "" keeps is_image |
ib_expanded | boolean | false | true 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_children | boolean | false | A 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_checked | boolean | false | The 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_enabled | boolean | true | false 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_visible | boolean | true | false takes the node and its subtree off the screen; it keeps its address |
is_badge | string | "" | A small badge after the text (markup accepted): a count of unread items; "" none |
ib_read_only | boolean | false | true: the user can neither rename nor drag it (it stays a target), and Delete leaves it out; your code still can |
ib_drop_target | boolean | true | false: nothing is dropped into it, neither a dragged node nor a file of the Explorer; before or after it stays allowed |
ib_show_checkbox | boolean | true | false: 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_tooltip | string | "" | Simple tooltip shown when hovering the item |
is_super_tooltip_title | string | "" | Title of the item rich tooltip (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the item rich tooltip (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the item rich tooltip |
il_accent | long | -1 | Accent 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_color | long | -1 | Background 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_color | long | -1 | Text colour of the row of the node (-1 = what the theme gives) |
il_back_color_hover | long | -1 | Background of the row of the node under the pointer (-1 = what the theme gives) |
il_text_color_hover | long | -1 | Text 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).
| Property | Type | Default | Purpose |
|---|---|---|---|
is_title | string | "" | The title in the header (markup accepted); "" shows the key |
ii_width | integer | 0 | The 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_align | string | ALIGN_START | Where 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_format | string | "" | 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_visible | boolean | true | false takes the column off the screen; it keeps its place and its key |
ii_index | integer | — | 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_sortable | boolean | true | A 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_summary | string | "" | 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_value | boolean | false | false: a branch that has its own value in the column keeps it (and brings it up); true: the total takes its place |
il_accent | long | -1 | Accent of the header: its sort arrow and the keyboard ring take it (-1 = the one of the component) |
il_back_color | long | -1 | Background of the header, as RGB(r,g,b) (-1 = what the theme gives) |
il_text_color | long | -1 | Text colour of the header (-1 = what the theme gives) |
il_back_color_hover | long | -1 | Background of the header under the pointer (-1 = what the theme gives) |
il_text_color_hover | long | -1 | Text colour of the header under the pointer (-1 = what the theme gives) |
is_tooltip | string | "" | Simple tooltip shown when hovering the header of the column |
is_super_tooltip_title | string | "" | Title of the rich tooltip of the header (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the rich tooltip of the header (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the rich tooltip of the header |
Constants #
They live on u_pbt_treeview: u_pbt_treeview.CHECK_MIXED.
| Constant | Value | Meaning |
|---|---|---|
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 #
| Method | Purpose | |
|---|---|---|
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 #
| Event | Raised 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) → boolean | Cancelable, 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) → boolean | Cancelable, 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) → boolean | Cancelable, 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) → boolean | Cancelable, 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 #
- Click on a node: it is selected (
ue_node_clicked); on its chevron: it opens or closes; on its box: it is checked; double click:ue_node_opened, and a branch opens or closes; right click:ue_node_rclicked. InSELECT_MULTIPLE, Ctrl + click adds or takes away a node, Shift + click takes the range from the last one. - Up / Down: the previous node, the next one; Home / End: the first, the last; Page Up / Page Down: by a screen. Greyed nodes are skipped.
- Right opens the node, then goes down to its first child; Left closes it, then goes up to its parent — inverted in a right-to-left application.
*of the numeric keypad opens the whole subtree;+/-of the numeric keypad open and close — those of the main keyboard are typed, to reach “+1.2” or “-Debits”. - Space checks the node (with
ib_checkboxes), otherwise selects it; Enter:ue_node_opened; F2: rename (withib_editable); context-menu key or Shift + F10:ue_node_rclicked. - In
SELECT_MULTIPLE, Shift + arrows extend the selection, Ctrl + arrows only move the focus, Ctrl + Space adds or takes away the node with the focus, Ctrl + A selects every node shown (one question withib_veto_selection). - The first letters of a text select the node shown that starts with them, as an arrow does (in
SELECT_NONE, they only bring the focus to it). - Delete:
ue_delete_requested(the tree removes nothing); Ctrl+C or Ctrl+Insert:ue_copy; F3 / Shift+F3: the next / previous match of the search (inSELECT_NONE, the focus goes there), from the tree or from the search field, where Enter does the same. Delete and Ctrl+C act on the node with the focus inSELECT_NONEonly. - The header of a tree list is one stop of the Tab key: Left / Right go from header to header (inverted in a right-to-left application), Home / End to the first and the last, Enter or Space sorts, Ctrl + Left / Right narrows or widens the column by 10 pixels, Ctrl + Shift + Left / Right moves it, Down goes to the rows.
- Drag a node (with
ib_drag_drop): a ghost follows the pointer, a bar shows where it will fall before or after, a frame when it falls inside; Escape cancels. Drag the edge of a header to widen the column, drag the header to move it — a ghost and an insertion bar show where, Escape cancels —, click the header to sort. A drag whose released button the tree cannot see — the window loses the focus (Alt+Tab, a message box) — is cancelled: nothing moves, a width comes back. - The tree is one stop of the Tab key (plus the header of a tree list, the field and the buttons of the toolbar); a dotted ring in the colour of the text marks the node with the focus. A greyed tree takes no focus.
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 #
- Stable keys: an id, not a label — the text is renamed, the key stays.
of_keys_atreturns whole addresses, to give as they are toof_node. A layout kept from one run to the next (of_get_layout) needs aROLE_KEYcolumn: without it, the keys of the rows are their RowIDs, which a newRetrievegives to other rows. - Branches on demand as soon as the tree is large or costly to read:
ib_has_childrenandue_children_neededread only what the user opens. - One DataStore per lot: loaded again for another node, a DataStore takes its rows — and PowerBuilder its RowIDs — away from the earlier lot, whose nodes then have no row any more.
- Many nodes: only the visible rows are drawn — ten thousand nodes scroll like twenty. Adding them is another matter: each
of_add_nodeis a message to the tree, about 0.7 ms (2,000 nodes ≈ 1.3 s measured, 10,000 ≈ 7 s expected); a large tree is loaded byof_from_datastore, in one message, or read on demand, one lot per branch (of_from_datastore(ads, as_parent_keys)). - The columns of a DataStore are ids as much as values: choose those you show with
of_add_column, nothing is created for you. - A check is a value: set by your code, it raises nothing; read
of_checked_keyswhen the user confirms. of_get_last_errorsays why the last call was refused; it is emptied by every method that acts and every property written, and byof_find_node/of_find_nodes, which say through it why they found nothing.- An order given during a wait (PowerBuilder code running while the tree waits for its page, a timer for instance) is not checked beforehand: the page decides alone, and an add or a move it refuses still returns
0. - During a wait (an event of the component being answered), an order of your code is not judged beforehand:
ib_expandedandof_move_nodepass the veto without asking the question, andof_get_last_errorsays it. Give your orders outside the event when the veto matters. of_set_layoutputs back a state — typically at the start — without asking the questions ofib_veto_selectionandib_veto_toggle: it is the exception to the rule that an order of the code asks the question.- Demo mode: without a key, the tree draws the first 8 nodes of each level and 200 nodes in the whole tree (the first ones added), in the order of the model — before the sort, the search and the hiding: a hidden node counts, a search or a sort never shows the ninth, lots and groups included, and a total never counts what the cap hides; the others stay in memory, neither shown nor selectable (see demo mode).
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.
| Members | Role | Detailed in |
|---|---|---|
of_count · of_keys_at · of_has | Walk what the component holds | 3.2 Items |
of_reset | Put the component back to zero | 3.6 Resetting a component: of_reset() |
of_register_shortcut · of_clear_shortcuts | The component's keyboard chords | 3.5 Keyboard shortcuts |
of_is_created · of_is_ready · of_get_last_error | Whether it was born, whether it is ready, what failed | 3.7 Diagnostics |
of_save_as_png · of_save_as_jpg | Export the rendering as an image | 3.8 Exporting the rendering as an image |
of_set_redraw | Group changes into a single repaint | 3.10 Best practices |
of_preload_icons | Icons shown with no delay | Instant display: of_icon |
of_set_translation | Translate one of the component's labels | 5.2 Adapting a label: of_set_translation |
of_focus_webview | Give the component the focus | 6.4 Keyboard and focus |
of_print · of_print_to_pdf | Print, or write a PDF | 6.9 Printing |
of_set_property · of_get_property · of_component_name | Driving a property by its name | 3.1 The property engine |
is_accessible_name · is_accessible_description | The name and description a screen reader says | 3.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.