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 #
| Userobject | u_pbt_contextmenu |
| Item class | n_pbt_contextmenu_item (of_item(keys)) |
| Used for | Offering, on a right-click or under a button, the commands that apply to what is at hand |
| Principle | You describe the entries by their address; the window, the keyboard and the cascades are ours |
| Opt-in option | ib_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:
of_show()— at the cursor, from the right-click of the control that receives the gesture — see the table below.of_show_under(control)— under a control: the menu 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. What a More or … button needs — and the keyboard:of_show_under(control, true)highlights the first enabled entry.of_show(x, y)— at a point of the screen, in pixels. Negative coordinates are a real point: a screen placed to the left of or above the main one.of_show_at(control, x, y)— at a point inside a control, in PowerBuilder units from its top-left corner: the pointPopMenutook, nothing to convert.of_source()then names that control inue_item_selected— one menu for several controls.
// 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 #
| Control | Event | Worth knowing |
|---|---|---|
| DataWindow | rbuttondown | Make the clicked row current first (SetRow(row)). The press is fine: only a press chooses an entry, never the release that follows it |
| ListView, TreeView | rightclicked | The event gives the clicked item |
| PBToolboxAI component | ue_rclicked | A component with items also has an item right-click event, which names the item by its address |
| Edit field | a user event on pbm_contextmenu | It 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 #
| Property | Type | Default | Role |
|---|---|---|---|
ib_track_hover | boolean | false | Switches 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_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) |
Methods #
| Method | Role | |
|---|---|---|
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 #
| Event | Fired 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 #
| Property | Type | Default | Role |
|---|---|---|---|
is_text | string | "" | 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_enabled | boolean | true | Set to false and the entry is greyed: shown, never chosen, its cascade closed |
ib_visible | boolean | true | Set 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_checked | boolean | false | A 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_checkable | boolean | false | A 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_check | boolean | false | Set 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_group | string | "" | 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_image | string | "" | Icon of the entry, changed in place. The same paths as of_add_item (mono:, tint:, file.dll:NAME); empty, it goes away |
is_shortcut | string | "" | 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_tone | string | "" | 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_accent | long | -1 | Accent of this entry: its check mark, and its edge while pointed at (-1 = the theme's) |
il_back_color | long | -1 | Background of this entry at rest (-1 = the theme's). Given alone, the text takes a colour that reads on it |
il_text_color | long | -1 | Text 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_hover | long | -1 | Background of this entry while it is pointed at (-1 = the theme's) |
il_text_color_hover | long | -1 | Text 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 #
- Open from the right-click event of the control (see the table): only a press chooses an entry, the release that follows chooses nothing.
- A short menu. The frequent commands at the top, the destructive ones at the bottom, after a separator, with
TONE_DANGER. - Grey out rather than hide a command that exists but does not apply here: the user learns where it is.
- Show the shortcuts of your commands: a context menu is where people learn them.
- One cascade, not two: beyond one level, a dialog reads better.
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.
| 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 |
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.