PBToolboxAI v5 ← Site

contextmenu — u_pbt_contextmenu #

← Component reference · Guide contents

The Windows context menu, in the theme of the application: a cascading list that opens at the cursor, under a control or at a point of the screen — icons, shortcuts shown, check marks, radio entries, section headers, long lists that scroll and answer to typing.

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


In brief #

Userobjectu_pbt_contextmenu
Item classn_pbt_contextmenu_item (of_item(keys))
Used forOffering, on a right-click or under a button, the commands that apply to what is at hand
PrincipleYou describe the entries by their address; the window, the keyboard and the cascades are ours
Opt-in optionib_track_hover

Quick start #

// The menu of a grid : three commands, a separator, a cascade
uo_menu.of_add_item(/*keys*/ "copy", /*text*/ "&Copy")
uo_menu.of_add_item(/*keys*/ "paste", /*text*/ "&Paste")
uo_menu.of_add_item(/*keys*/ "delete", /*text*/ "&Delete")
uo_menu.of_add_separator(/*keys*/ "")
uo_menu.of_add_item(/*keys*/ "export", /*text*/ "&Export")
uo_menu.of_add_item(/*keys*/ "export/pdf", /*text*/ "PDF")
uo_menu.of_add_item(/*keys*/ "export/csv", /*text*/ "CSV")

// It opens from the right-click event of the grid : see Where to call of_show
// event ue_item_selected of uo_menu : (string as_keys)
// as_keys is the ADDRESS of the chosen entry
choose case as_keys
	case "copy"
		of_copy_selection()
	case "export/pdf"
		of_export("pdf")
end choose

Opening the menu #

The component itself is invisible: it takes no room in the window. Drop it anywhere, zero width and height — it only exists while the menu is open. Four ways to open it:

// event rbuttondown of a DataWindow : the row under the cursor first
if row > 0 then dw_list.SetRow(/*row*/ row)
uo_menu.of_show()

// event ue_clicked of a "More" button : the menu hangs from it
uo_menu.of_show_under(/*control*/ uo_more)

// event rbuttondown of a DataWindow, another way : the point in the control, in
// PowerBuilder units, as PopMenu took it
uo_menu.of_show_at(/*control*/ dw_list, /*x*/ dw_list.PointerX(), /*y*/ dw_list.PointerY())

Where to call of_show #

ControlEventWorth knowing
DataWindowrbuttondownMake the clicked row current first (SetRow(row)). The press is fine: only a press chooses an entry, never the release that follows it
ListView, TreeViewrightclickedThe event gives the clicked item
PBToolboxAI componentue_rclickedA component with items also has an item right-click event, which names the item by its address
Edit fielda user event on pbm_contextmenuIt comes on a right-click; return 1 keeps the field from also opening its own Windows menu (Cut, Copy, Paste). The keyboard does not come there under PowerBuilder 10: see the recipe below

of_show() opens where the cursor is, that is where the user clicked: no unit to convert. of_show(x, y) expects screen pixels, never the PowerBuilder units of an xpos/ypos relative to the control. For a point inside a control, in PowerBuilder units, use of_show_at(control, x, y).

From the keyboard — Shift+F10 or the Menu key — the mouse is anywhere: open the menu under the control that has the focus, with of_show_under(control, true). As in Windows, its first enabled entry is then highlighted: Enter chooses it, the arrows go on from there. Under PowerBuilder 10, pbm_contextmenu only receives the right-click, and Shift+F10 does not reach an edit field (measured: neither pbm_contextmenu, nor pbm_keydown, nor pbm_syskeydown). The keyboard key that opens the menu on a PowerBuilder control is therefore the Menu key (KeyApps!), taken in a user event on pbm_keydown — pbm_dwnkey for a DataWindow, whose right-click goes through its rbuttondown event: pbm_contextmenu does not come there for a click in a cell (measured). Shift+F10 is still received by the PBToolboxAI components themselves (their ue_rclicked).

// User event ue_contextmenu of an edit field, mapped on pbm_contextmenu :
// the right-click opens the menu at the cursor
uo_menu.of_show()

// The field does not open its own Windows menu as well
return 1
// User event ue_key of the same field, mapped on pbm_keydown : the Menu key
// opens the menu under the field, its first entry highlighted
if key = KeyApps! then
	uo_menu.of_show_under(/*control*/ this, /*keyboard*/ true)
	return 1
end if

// User event ue_key of a DataWindow, mapped on pbm_dwnkey : the Menu key
if key = KeyApps! then
	uo_menu.of_show_under(/*control*/ this, /*keyboard*/ true)
	return 1
end if

One answer per opening: an open menu ends with a choice (ue_item_selected) or without one (ue_dismissed — a click outside, Escape, of_close). A menu with nothing to show does not open: ue_dismissed comes at once. While the menu is on screen the application keeps running: an entry greyed, hidden or removed in the meantime can no longer be chosen (ue_dismissed instead), and of_remove_item, of_clear or of_reset close the open menu. ue_dismissed(as_reason) says why: REASON_ESCAPE (Escape), REASON_OUTSIDE (a click outside), REASON_FOCUS (another window took the focus), REASON_USER (another way, another menu taking over), REASON_CODE (of_close, an entry removed or the menu cleared), REASON_REPLACED (a second of_show), REASON_EMPTY (nothing to show), REASON_UNAVAILABLE (the chosen entry was no longer available).

One menu at a time: a second of_show replaces the open menu, which answers ue_dismissed first. And of_close called right after of_show closes the menu before it is even drawn — ue_dismissed follows too.

From the keyboard, the arrows walk the entries, stepping over greyed ones, → opens a cascade and ← closes it, Enter chooses, Escape climbs one level then closes. The underlined letter — the one after & in the label, && for an ampersand — chooses the entry straight away; elsewhere, typing the first letters of a label goes to that entry, spaces included ("report 2"). A long list (five hundred entries and more) scrolls and stays fluid. Read right to left, the menu aligns on the right and the cascades open to the left.


Cascades, sections and marks #

An entry is named by its address: its key alone at the top (copy), the keys of its parents then its own, joined by /, in a cascade (export/pdf). The parent must exist; a key holds neither / nor | and does not start with __. ue_item_selected hands back that same address: two cascades may name their entries alike.

A separator (of_add_separator) and a section header (of_add_header) go at the end of the menu (empty address) or of the cascade of an entry. Neither is an entry: never chosen, never counted. A separator is never shown leading, trailing or doubled; a header whose entries are all hidden goes with them.

A check mark (ib_checked) says an option is on; a radio entry (is_group) is one choice among several: the entries of one group under the same parent exclude each other — checking one, by the code or by the user's choice, unchecks the others, and a round bullet replaces the tick. The shortcut (is_shortcut) is shown at the right of the entry; it is shown only: the key belongs to the control the menu is about (an edit field already copies on Ctrl+C), and a context menu that armed it would take it from every control of the window.

A checked entry outside a group does not toggle by itself when the user chooses it — as in every Windows menu: switch it in ue_item_selected. A command that destroys stands out by its tone: is_tone = TONE_DANGER writes its text in the red of the theme, readable in light and dark alike. For an option that switches itself when chosen, set ib_auto_check = true: the mark changes before ue_item_selected. An option is a check box — announced "not checked" by a screen reader — once it has been checked, or as soon as ib_checkable = true.

// A section of three exclusive entries in the View cascade
uo_menu.of_add_header(/*keys*/ "view", /*text*/ "Layout")
uo_menu.of_add_item(/*keys*/ "view/list", /*text*/ "&List")
uo_menu.of_add_item(/*keys*/ "view/details", /*text*/ "&Details")
uo_menu.of_item(/*keys*/ "view/list").is_group = "layout"
uo_menu.of_item(/*keys*/ "view/details").is_group = "layout"

// Checking one unchecks the other
uo_menu.of_item(/*keys*/ "view/details").ib_checked = true

// The shortcut SHOWN at the right of an entry
uo_menu.of_item(/*keys*/ "copy").is_shortcut = "Ctrl+C"

// A command that destroys : the red of the theme
uo_menu.of_item(/*keys*/ "delete").is_tone = uo_menu.TONE_DANGER

// event ue_item_selected : a check mark outside a group is switched by you
if as_keys = "wrap" then uo_menu.of_item(/*keys*/ as_keys).ib_checked = not uo_menu.of_item(/*keys*/ as_keys).ib_checked

// ... or by the entry itself, before ue_item_selected
uo_menu.of_item(/*keys*/ "grid").ib_auto_check = true

Taking over a PowerBuilder menu #

Your popup menus are already written? of_load_menu makes them the menu of the component: pass the item you called PopMenu on. Each item becomes an entry, cascades included, with its text (& included), its state (Enabled, Checked, Visible), its Shortcut shown at the right — as is the text after a tab — and its toolbar picture when it is a file that exists. Its key is the ClassName of the item, at every level. On a choice, the Clicked script of the item runs first, then ue_item_selected: the code written for PopMenu keeps working. The menu stays alive, as with PopMenu: each opening reads the items again (Text, Enabled, Checked, Visible, Shortcut). The code that came before PopMenu — m_paste.Enabled = (Clipboard() <> "") — and a Clicked that does this.Checked = not this.Checked therefore show at the next opening; an item greyed or hidden while the menu is open does not run its Clicked (ue_dismissed(REASON_UNAVAILABLE)). Keep the menu object alive for as long as the component uses it. of_loaded_item returns the item of an entry, to read what only the menu knows (its MicroHelp) — test it with IsValid, an entry added by hand has none. A Clicked script that reads ParentWindow finds a valid window there — measured under PowerBuilder 10, even for a menu created with create and never attached to a window — but PowerBuilder chooses it: nothing says it is the window that holds the context menu. For a Clicked to reach one given window, give it to the menu in an instance variable, set before of_load_menu, or move that code to ue_item_selected.

// Instance variable of the window : the menu lives as long as it does
// m_popup im_popup

// open event of the window : the items of m_popup.m_edit become the menu
im_popup = create m_popup
uo_menu.of_load_menu(/*menu*/ im_popup.m_edit)

// event rbuttondown of the DataWindow : the code before PopMenu stays,
// of_show replaces PopMenu - the items are read again as it opens
im_popup.m_edit.m_paste.Enabled = (Clipboard() <> "")
uo_menu.of_show()

// event ue_item_hover of uo_menu (ib_track_hover = true) : the MicroHelp
if IsValid(uo_menu.of_loaded_item(/*keys*/ as_keys)) then st_help.text = uo_menu.of_loaded_item(/*keys*/ as_keys).MicroHelp
// A Clicked that must reach ONE given window : the menu keeps that window
// itself. Instance variable of m_popup : window iw_owner
// Clicked of m_edit.m_copy
iw_owner.TriggerEvent("ue_copy")

// open event of the window, before of_load_menu
im_popup.iw_owner = this

Properties #

PropertyTypeDefaultRole
ib_track_hoverbooleanfalseSwitches on ue_item_hover, which tells which entry the pointer or the keyboard is on — for the help line of a status bar. Without it the menu does not even report it: an event raised at every move is only sent to an application that asked for it — set it before of_show: changed while a menu is open, it counts from the next opening
is_theme_stylestring""Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change
is_theme_modestring""Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change
il_theme_accentlong-1Accent colour of this component (-1 = the application accent, or the theme's)

Methods #

MethodRole
of_add_header (string as_keys, string as_text)Adds a section header at the end of the menu (empty address) or of the cascade of an entry: a title line over the entries that follow it. Never chosen nor counted. Returns 0 once added, -5 when no entry lives at that address, -2 when the component is not created
of_add_item (string as_keys, string as_text)Adds an entry at its address: copy in the menu, export/pdf in the cascade of an existing export entry, as deep as needed. An empty label shows the key; & marks the letter that chooses it from the keyboard. Returns 0 once added, -5 when the address is refused (parent not found, address already taken, an empty level such as export/, a key holding ` or starting with __), -2` when the component is not created
of_add_item (string as_keys, string as_text, string as_image, boolean ab_checked)Same, with the icon and the check mark — the icon third, as everywhere else in the library. An entry is greyed through its handle: of_item(keys).ib_enabled = false. Returns 0 once added, -5 when the address is refused, -2 when the component is not created
of_add_separator (string as_keys)Adds a separator at the end of the menu (empty address) or of the cascade of an entry. Returns 0 once added, -5 when no entry lives at that address, -2 when the component is not created
of_clear ( )Empties the menu and frees the handles obtained through of_item. An open menu closes, and ue_dismissed says so. Returns 0 once applied, -2 when the component is not created
of_close ( )Closes the menu of the component if it is on screen, without a choice: ue_dismissed follows, as for a click outside. Nothing open, nothing happens. Returns 0 once asked, -2 when the component is not created
of_insert_item (string as_keys, string as_text, long al_index)Inserts an entry at a rank of its list rather than at its end: as_keys is the address it will have, al_index its place among the entries of that list, from 1 (0 or less = the first, past the end = the end). Separators and headers do not count: the entry goes just before the one that held that rank. Returns 0 once added, -5 when the address is refused (as of_add_item), -2 when the component is not created
of_insert_item (string as_keys, string as_text, string as_image, boolean ab_checked, long al_index)Same, with the icon and the check mark, as of_add_item takes them. Returns 0 once added, -5 when the address is refused, -2 when the component is not created
of_is_open ( )true while a menu of the component is on screen — opened, and not answered yet by a choice or a dismissal
of_item (string as_keys)Returns the handle of an entry, by its address, to read or change it later (label, state, icon, shortcut, group, colours). A bare key is resolved into the full address of the one entry that carries it: of_item("pdf") and of_item("export/pdf") then return the same handle. Ambiguous, or carried by no entry, it returns a handle that changes nothing rather than the wrong entry
of_load_menu (menu am_menu)Builds the menu from a PowerBuilder menu: pass the item you called PopMenu on. Its items become the entries, cascades included, key = ClassName; on a choice, the Clicked of the item runs before ue_item_selected. The items are read again at each opening. The menu replaces what the component held. Returns 0 once loaded, -5 when the menu is not valid, -2 when the component is not created
of_loaded_item (string as_keys)The PowerBuilder menu item an entry came from (of_load_menu), by its address — to read its MicroHelp. An address that did not come from a menu returns an invalid reference: test it with IsValid
of_remove_item (string as_keys)Removes one entry and its cascade; the others stay. Its handles are freed; an open menu closes (ue_dismissed). Returns 0 once removed, -5 when no entry lives at that address, -2 when the component is not created
of_reset ( )Empties the menu and puts every property back to its default, without raising any event. Returns 0 once applied, -2 when the component is not created
of_show ( )Opens the menu at the cursor. Call it from the right-click of the control that receives the gesture. A menu with no visible entry does not open: ue_dismissed is raised. Returns 0 once asked, -2 when the component is not created
of_show (long al_x, long al_y)Same, at a screen position in pixels. Negative coordinates are a real point: a screen placed to the left of or above the main one. Returns 0 once asked, -2 when the component is not created
of_show_at (dragobject ado_control, long al_x, long al_y)Opens the menu at a point inside a control, in PowerBuilder units from its top-left corner — the point PopMenu took: of_show_at(dw_list, dw_list.PointerX(), dw_list.PointerY()). The control is kept: of_source names it. Returns 0 once asked, -5 when the control is not valid or a coordinate is null, -2 when the component is not created
of_show_under (dragobject ado_control)Opens the menu under a control: it hangs from its bottom edge, aligned on its start edge (its right edge in a right-to-left layout), and opens above it when the screen ends below. Returns 0 once asked, -5 when the control is not valid, -2 when the component is not created — under a component with items (a buttonbar, a toolbar), the menu hangs under the whole component, not under one of its buttons
of_show_under (dragobject ado_control, boolean ab_keyboard)Same, saying how the menu was asked for: true for the keyboard (Shift+F10, the Menu key) — the first enabled entry is highlighted, Enter chooses it, as in Windows. Returns 0 once asked, -5 when the control is not valid, -2 when the component is not created
of_source ( )The control the menu on screen — or the last one — was opened from: the one given to of_show_under or of_show_at. Read it in ue_item_selected when one menu serves several controls. A menu opened at the cursor or at a screen point (of_show) has none: the reference is invalid, test it with IsValid

Events #

EventFired when
ue_item_selected (string as_keys)The user chose an entry, by the mouse or the keyboard. as_keys is its full address (export/pdf). An entry of a group is checked, and the others of its group unchecked, before the event
ue_dismissed (string as_reason)The menu closed without a choice: a click outside, Escape, of_close, an entry removed or the menu cleared while it was open, a second of_show that replaced it — and also when of_show had nothing to show — as_reason says which: REASON_ESCAPE, REASON_OUTSIDE, REASON_FOCUS, REASON_USER, REASON_CODE, REASON_REPLACED, REASON_EMPTY, REASON_UNAVAILABLE (an entry greyed, hidden or removed since the opening)
ue_item_hover (string as_keys)With ib_track_hover = true: the entry the pointer is on, by its address — a greyed entry too, so its help can say why; from the keyboard, the entries it stops on, stepping over greyed ones. Raised once per entry, then with an empty address when the menu closes: empty the help line then. of_reset closes an open menu without raising it: empty it yourself

Item properties #

PropertyTypeDefaultRole
is_textstring""Label of the entry. & marks the letter that chooses it from the keyboard (&& for an ampersand); empty, the key is shown and the label reads back empty
ib_enabledbooleantrueSet to false and the entry is greyed: shown, never chosen, its cascade closed
ib_visiblebooleantrueSet to false and the entry leaves the menu — cascade included — without being removed: it keeps its key and its state, and comes back in its place. A cascade whose entries are all hidden stays, greyed
ib_checkedbooleanfalseA check mark on the entry — a round bullet for an entry of a group. Checking an entry of a group unchecks the others of the group; outside a group, the entry does not toggle by itself when chosen: do it in ue_item_selected, or set ib_auto_check
ib_checkablebooleanfalseA check box: the option is shown and announced as one even unchecked ("not checked" for a screen reader). An entry checked once becomes one by itself; set it for an option that starts unchecked. A radio entry (is_group) never is one
ib_auto_checkbooleanfalseSet to true, choosing the entry switches its check mark by itself, before ue_item_selected — ib_checked tells the new state there. It becomes a check box; no effect on an entry of a group
is_groupstring""Makes the entry a radio entry of the named group: the entries of one group under the same parent exclude each other. Empty: an ordinary entry
is_imagestring""Icon of the entry, changed in place. The same paths as of_add_item (mono:, tint:, file.dll:NAME); empty, it goes away
is_shortcutstring""The shortcut shown at the right of the entry (Ctrl+C). Shown only: the key belongs to the control the menu is about. Never shown on an entry that opens a cascade
is_tonestring""The tone of the command: its text, at rest, in a colour of the light or dark theme — TONE_DANGER for the one that destroys, TONE_WARN, TONE_SUCCESS, TONE_INFO; empty or TONE_NEUTRAL = the text of the theme. Pointed at, the entry takes the hover colours of the theme; an il_text_color of the entry wins. The constants are on u_pbt_contextmenu
il_accentlong-1Accent of this entry: its check mark, and its edge while pointed at (-1 = the theme's)
il_back_colorlong-1Background of this entry at rest (-1 = the theme's). Given alone, the text takes a colour that reads on it
il_text_colorlong-1Text colour of this entry (-1 = the theme's). One colour does not read on the light and the dark theme: set il_back_color with it, or prefer is_tone, which follows the theme
il_back_color_hoverlong-1Background of this entry while it is pointed at (-1 = the theme's)
il_text_color_hoverlong-1Text colour of this entry while it is pointed at (-1 = the theme's): set il_back_color_hover with it, the hover of the theme changes from one theme to the next

An entry shows no tooltip: the native menu has none. The is_tooltip the handle inherits is kept and reads back, but it is never shown — for help, ib_track_hover and ue_item_hover.


Examples #

A menu for what was clicked #

// event ue_rclicked of the list : the menu is rebuilt for the current row
uo_menu.of_clear()
uo_menu.of_add_item(/*keys*/ "open", /*text*/ "&Open")
uo_menu.of_add_item(/*keys*/ "rename", /*text*/ "Re&name")
uo_menu.of_add_separator(/*keys*/ "")
uo_menu.of_add_item(/*keys*/ "delete", /*text*/ "&Delete")

// A row the user may not change : its commands are greyed
uo_menu.of_item(/*keys*/ "rename").ib_enabled = ib_can_edit
uo_menu.of_item(/*keys*/ "delete").ib_enabled = ib_can_edit

// At the cursor
uo_menu.of_show()

A More button #

// event ue_clicked of the button : the menu hangs from it
uo_menu.of_show_under(/*control*/ uo_more)

A help line in the status bar #

// Once, after the menu is built : report the pointed entry
uo_menu.ib_track_hover = true

// event ue_item_hover of uo_menu : (string as_keys), "" when the menu closes
choose case as_keys
	case "delete"
		st_help.text = "Deletes the row, without the clipboard."
	case else
		st_help.text = ""
end choose

Good practice #

Demo version #

Without a key, each list of the menu — the root as each cascade — shows its first six visible entries; separators and headers do not count. The last line, greyed, says how many entries are not shown — or, with nothing cut, reads "PBToolboxAI — DEMO". The model keeps everything: a key shows the whole menu at the next opening.

The launcher has no surface: of the inherited members below, of_save_as_png, of_print and of_focus_webview have nothing to capture, print or receive. of_register_shortcut stays useful — a window shortcut that opens the menu with of_show_under.

Inherited from the common base #

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

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

On this invisible launcher, of_count and of_keys_at walk what you added, hidden branches included — not what the wheel will draw. Exporting as an image, printing, of_focus_webview and of_set_redraw have no useful effect here: the component draws nothing of its own, the wheel lives in its own window.

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


← Component reference · Guide contents