commandpalette — n_pbt_commandpalette #
← Component reference · Guide contents
Command palette: the user presses a chord, types three letters, and reaches any action in your application — without hunting through the menus.
▶ See it live — Demo application, Command palette tile: the preview, the code behind it and this page, side by side.
At a glance #
| Object | n_pbt_commandpalette — non-visual: nothing to drop on the window |
| Used for | Making every action of the application reachable from the keyboard, in three letters |
| Return | Non-blocking: of_open() returns at once; the choice comes back as an event |
The palette is a detached window of its own: it floats above your application, takes the focus while the user types, and hands it back on closing.
Quick start #
Place n_pbt_commandpalette on your window as a non-visual object (named inv_palette here): its events are scripted on it, like a control's.
// Once, at startup : your application's actions
inv_palette.ipo_owner = this
inv_palette.of_add_command(/*key*/ "new", /*label*/ "New project", /*group*/ "File")
inv_palette.of_add_command(/*key*/ "open", /*label*/ "Open file...", /*group*/ "File")
inv_palette.of_add_command(/*key*/ "save", /*label*/ "Save", /*group*/ "File")
inv_palette.of_register_shortcut()
// event ue_command_selected of inv_palette : (string as_key)
// parent is the window : the palette runs nothing by itself
choose case as_key
case "new"; parent.wf_new()
case "open"; parent.wf_open()
case "save"; parent.wf_save()
end choose
The wiring: the host window #
The palette is a non-visual object, but you have no receiver and no message to wire: it drains its own events and raises them on itself — ue_command_selected, ue_shortcut, ue_opened and ue_closed are scripted on the palette object placed on the window (or on a descendant of it). ipo_owner says which window it belongs to.
Two lines, once, when the window opens:
// The window the palette belongs to
inv_palette.ipo_owner = this
// The palette has to answer its key
inv_palette.of_register_shortcut()
That is all there is to wire. The user's choice, the opening and the closing then come back to you as plain events of the palette object (not of
ipo_owner) — no receiver, no message to map, no timer. An event may open a MessageBox or close the window ("Exit"): the next ones wait until it is done, and a palette destroyed with its window stops cleanly.
Where to write the events #
PowerBuilder offers the events of a non-visual object in two places only: the palette placed on the window (in the painter, Insert > Object, then n_pbt_commandpalette, named inv_palette: its events are scripted like a control's — this is what the examples of this page assume), or a descendant of n_pbt_commandpalette (n_my_palette), whose events carry your scripts and that the window creates by create n_my_palette. A n_pbt_commandpalette variable made by create has no script: the palette opens, but the user's choice reaches nobody.
The chord: the DLL is the one that hears it #
The chord that opens the palette is not listened to by the page: it is registered with the DLL, which alone sees the keys pressed while the focus is on another control. That is the whole difference between a palette people find and a palette that only answers once you have clicked on it.
The DLL hears the chord, but it opens nothing by itself: it tells you through ue_shortcut, and you decide. A palette opening over a modal dialog would help nobody.
// event ue_shortcut : the key has fired
if not ib_dialog_open then inv_palette.of_open()
is_shortcut picks the chord; of_register_shortcut() hands it over. Call it once when the window opens — otherwise the palette only answers its key after having been opened once already. of_open hands it over again on the way, so a chord changed later needs nothing more.
// The chord everyone already knows, from the code editors
inv_palette.is_shortcut = inv_palette.SHORTCUT_DEFAULT
// Or yours
inv_palette.is_shortcut = "ctrl+shift+p"
// Or none: the palette then opens through of_open() only
inv_palette.is_shortcut = inv_palette.SHORTCUT_NONE
The palette's chord is looked up only after the shortcuts of the window's other components, focus or not: a toolbar button on the same chord wins. The chord of this window comes before a chord registered without
ipo_owner(the whole application). Only the keys the hook can name are accepted — letters, digits, F1 to F24, Enter, Esc, Del, Ins, Home, End, PgUp, PgDn and, with Ctrl or Alt, the arrows, Space, Tab, Backspace,+ - , .; any other returns-5. The keyboard chapter has the detail.
Where the palette appears #
is_position says where the window lands. It is always brought back inside the screen: a palette anchored under a field at the bottom of the window does not disappear behind the taskbar.
| Constant | Where |
|---|---|
POSITION_WINDOW_CENTER | Centred on ipo_owner — the default, and what the eye expects |
POSITION_SCREEN_CENTER | Centred on the screen, whatever the window |
POSITION_ABSOLUTE | At il_x / il_y, in screen pixels |
PowerBuilder works in PBU, and a control's position is relative to its window: to anchor the palette under a control, of_anchor_under(control) does the conversion and sets all three properties. On two screens, it opens on the screen of the requested point.
Its height follows the number of commands shown, and shrinks as you filter — without its top corner moving, or the search box would slide away under your fingers. It is capped at half the screen: past that the list scrolls inside it and the search box stays at the top.
A click elsewhere in the application closes the palette, and that click still reaches its target — like leaving a menu. Nothing to do for it.
Properties #
| Property | Type | Default | Role |
|---|---|---|---|
ipo_owner | powerobject | — | The window the palette belongs to: it owns the popup window and anchors it, and its chord answers in that window. Set it before of_open. Left unset, the chord answers in every window of the application. The events are not raised on it but on the palette object itself. Two palette objects on the same window each keep their own chord and their own events: neither receives the other's |
ipo_receiver | powerobject | — | Optional, legacy: a window whose pbm_custom02 is rung at each event, for older code that called of_process_events from it. Leave it empty — the palette delivers its events on its own, on itself |
is_shortcut | string | "ctrl+k" | Chord that opens the palette, from anywhere in the window. Constants SHORTCUT_DEFAULT (ctrl+k) and SHORTCUT_NONE (none). Takes effect on of_register_shortcut |
is_position | string | window-center | Where the window lands (POSITION_* constants) |
il_x · il_y | long | 0 | Position in screen pixels, read by POSITION_ABSOLUTE only |
is_placeholder | string | "" | Grey text shown in the search box while nothing has been typed |
is_recent | string | "" | Usage memory: the ids most recently launched, newest first, comma separated. The palette floats them to the top; when filtering, recency only breaks ties between results that match about as well — it never lifts a weaker match above a better one, whatever il_max_recent is. Spaces around the ids and repeated ids are cleaned up, and the cleaned list comes back here. Read it back after use and persist it; set it at startup. A palette that starts blank every morning learns nothing |
il_max_recent | long | 8 | How many the « Recently used » block holds. 8 by default. A lower limit cuts is_recent at the next opening. 0 switches it off: an application whose users would rather see their groups untouched can say so — is_recent is then kept as it is, unused. An entry in the block stays in its group and carries that group's name — a shortcut does not move what it is a shortcut to |
Methods #
| Method | Role | |
|---|---|---|
of_add_command (string as_key, string as_label, string as_group) | Declares an action: its identifier, its label, and the group it appears under. Returns 0 once added, -5 when the key is empty, holds /, ` | or a comma (is_recent is a comma separated list), or is already taken. Tens of thousands of commands stay fluid: the palette only draws the lines that can be seen. A command with an empty group comes **first**, without a header; of_get_last_error` says why a key was refused |
of_add_command (string as_key, string as_label, string as_group, string as_hint, string as_shortcut, string as_keywords) | Same, with the hint on the right, the chord to display, which runs its command while the palette is open — that is how one learns it ; outside, your application keeps its own accelerators, and while one types Ctrl+C, Ctrl+V, Ctrl+Z and Ctrl+A stay with the search box — and keywords the search reads without showing them. Returns 0 once added, -5 when the key is empty, holds /, ` | or a comma (is_recent is a comma separated list), or is already taken. A command with an empty group comes **first**, without a header; of_get_last_error` says why a key was refused |
of_insert_command (string as_key, string as_label, string as_group, long al_index) | Declares an action at a chosen rank (1 = first) rather than at the end: a shared module puts its commands where they belong. 0 or less, or past the end, appends. Hint, shortcut, keywords and icon are set afterwards through of_command. Returns 0 once added, -5 for the same keys as of_add_command | |
of_insert_command (string as_key, string as_label, string as_group, string as_hint, string as_shortcut, string as_keywords, long al_index) | The full form, as of_add_command takes it: the hint, the shortcut to display and the keywords come with the command, at its rank. Same returns | |
of_remove_command (string as_key) | Removes one action; the others stay. Returns 0 once removed, -5 when no command has that key | |
of_command (string as_key) → n_pbt_commandpalette_command | The handle to a command, to rename it, change its chord, grey it or hide it through its properties. Greying rather than removing: removing what the user cannot do right now also removes any chance of discovering that it exists. The state travels with the commands: a change made while the palette is open shows at the next opening. A key no command has gives a handle that designates nothing: its booleans read FALSE, its texts "", and a write is lost — of_get_last_error says so | |
of_key ( ) → string | On the handle of_command returns: the key of the command it designates — what of_command took to get it, and what to keep when the handle is passed around | |
of_clear_commands ( ) | Empties the palette, the commands read from a menu (of_load_menu) included. Returns 0 | |
of_count ( ) → long | Returns how many commands the palette holds | |
of_keys_at ( long al_index ) → string | The id of the command at rank al_index (1 based), or "" past either end. With of_count, this is what lets you walk a palette you did not fill yourself — a shared module adds its own | |
of_has ( string as_key ) → boolean | Does a command exist under this id? Asking beats guessing: of_add_command refuses (-5) an id already taken | |
of_anchor_under ( dragobject ado_control ) | Anchors the palette under a control — a field, a button: sets is_position to POSITION_ABSOLUTE and il_x / il_y to the bottom-left corner of the control, in screen pixels. Call it before of_open; the palette is still brought back inside the screen. Returns 0, or -5 when the control is not valid. A control (or window) that reads right to left gives its bottom-right corner: the palette is aligned on that edge and opens towards the left | |
of_open ( ) | Opens the palette: a window of its own, owned by ipo_owner, placed by is_position. It takes the focus, and gives it back on closing. Returns 0 once asked for — ue_opened (on screen) or ue_closed (with the reason it never showed) always follows —, -6 when the WebView2 runtime is missing, -4 when its window could not be created. The commands read from a menu (of_load_menu) take the state of their item at that moment | |
of_is_open ( ) | TRUE while the palette is on screen. This is what lets the chord toggle: pressed a second time a palette closes — calling of_open again would destroy the window and rebuild it identically, which reads as a flicker, not as a close. The DLL still decides nothing: it informs. Once open, the palette holds the focus in its own window: the chord pressed there closes it by itself. It answers for this object's palette: when another palette object opens its own, it replaces this one and of_is_open answers FALSE here | |
of_close ( ) | Closes the palette this object opened — never the one another palette object opened since. Losing the focus closes it too, like a menu. Returns 0 | |
of_register_shortcut ( ) | Hands the chord of is_shortcut to the DLL. Call it once when the window opens. One chord per palette object: calling it again after changing is_shortcut replaces the previous one, which stops answering at once — there is never anything to remove first. Returns 0 when set, 1 when it replaced one, 2 when an empty is_shortcut leaves no chord (removed, or there was none), -5 when the key is one the hook cannot see (see above) — the previous chord is then kept. Without ipo_owner, the chord answers in every window of the application. Two palette objects of one window each keep their own; the same chord registered by a second object goes to it (1). Destroying the object removes its chord — never the one another palette object holds. The chord taken over by a second object comes back to the first one when the second is destroyed. While a window is active, the palette's chord comes before the accelerators of its PowerBuilder menu: a menu item on the same key no longer hears it | |
of_process_events ( ) | Drains the queued events and raises them on the palette object itself. The component calls it on its own as long as the palette lives: you do not have to. An event that opens a MessageBox does not get the next ones raised inside it, and an event that closes the window holding the palette stops the drain cleanly | |
of_reset ( ) → long | Empties the commands and the usage memory (is_recent), returns the properties to their defaults, closes an open palette and brings a registered chord back to SHORTCUT_DEFAULT. ipo_owner and ipo_receiver are left alone: they are the wiring, not the content. The commands read from a menu go too Returns 0 once done. | |
of_load_menu ( menu am_menu ) | Fills the palette from a PowerBuilder menu: every item without sub-items becomes a command, without declaring your actions twice — pass this.MenuID or one of its items. Key = the ClassName of the item, label = its text without & nor the shortcut written after a tab, group = the texts of the items above it, shortcut = the one it shows, keywords = its MicroHelp. The menu stays the master: read again at each of_open, an item greyed, hidden or renamed shows as it is (a parent takes its items with it). Choosing a command raises ue_command_selected and runs nothing. A second call replaces the commands of the first; those of of_add_command stay. Returns 0, -5 when the menu is not valid | |
of_loaded_item ( string as_key ) → menu | The PowerBuilder menu item a command came from (of_load_menu): in ue_command_selected, of_loaded_item(as_key).TriggerEvent(Clicked!) runs what the menu runs. A key that did not come from a menu gives an invalid reference: test it with IsValid | |
of_get_last_error ( ) → string | Why the last call returned -5 (a key refused, a menu not valid, a chord the keyboard cannot report), or the last write of a command handle that reached no command. "" when nothing went wrong: each call that acts empties it first |
Command properties — n_pbt_commandpalette_command #
Obtained through of_command(key). The palette rebuilds its window from its list at each of_open: a property changed while it is open shows at the next opening.
| Property | Type | Default | Role |
|---|---|---|---|
is_label | string | — | The text of the row |
is_shortcut | string | "" | The chord shown at the right of the row, and honoured while the palette is open (Ctrl+Shift+S) |
is_group | string | — | The group the command is listed under; changing it moves the command without removing it (it keeps its rank among the commands) |
is_hint | string | "" | The small line under the label |
is_keywords | string | "" | The words the search reads without showing them — the user's words |
is_icon | string | "" | An icon on the left of the row: a file, a library image, or mono: / tint: for one that follows the theme |
ib_enabled | boolean | true | Command greyed: visible, searchable, and inert — neither click, Enter nor its chord |
ib_visible | boolean | true | Command hidden: out of the list and of the chords, without being removed; it comes back as it was |
Events #
| Event | Raised when |
|---|---|
ue_command_selected (string as_key) | The user picked an action. The palette has already closed: doing what it announces is up to you. The left button, Enter or the chord shown on a row choose; a right or middle click does nothing. The event may open a MessageBox or close the window |
ue_shortcut ( ) | The chord was pressed. The DLL relays, PB decides. A palette toggles on its own key: if of_is_open() then of_close() else of_open() — pressed inside the open palette, the chord closes it by itself. You may also refuse |
ue_opened ( ) | The palette is on screen — through of_open |
ue_closed (string as_reason) | It has just closed, whether or not something was picked. It follows every of_open that returned 0: as_reason is empty for a palette that was on screen, cancelled when it was closed — or replaced by another palette — before it showed, failed when its window could not be built, blocked under remote debugging without a licence |
The palette does nothing by itself. It reports the identifier picked, and closes. Your application is what acts — the same action, triggered from a menu or from the palette, therefore goes through the same code.
From the keyboard #
| Key | Effect |
|---|---|
The chord of is_shortcut | Tells your code through ue_shortcut; that is what opens it |
| Typing | Filters as you type: the letters need not follow each other, nwf finds "New file", and accents do not count (preferences finds « Préférences ») |
| Up / down arrows | Move the selection through the list |
| Enter | Picks the selected action (ue_command_selected) |
| The chord shown on a row | Runs that command, without having to select it first |
| Escape | Closes without picking anything |
| Page up / Page down | Move the selection one screenful |
| Home / End | With text typed: to the start / end of the query. With an empty field: to the first / last command. Ctrl+Home / Ctrl+End: always the first / last command; Shift+… selects in the field |
| Tab | Stays in the search box (the palette is a modal dialog) |
| A right or middle click on a row | Does nothing: only the left button chooses |
While an input method (Japanese, Chinese, Korean) is composing, Enter and the arrows belong to it: confirming a conversion never runs a command.
Examples #
Feeding the palette from your menu #
// Every action of your menu becomes a command : nothing declared twice
inv_palette.of_load_menu(/*menu*/ this.MenuID)
// The palette answers its key from the window's opening
inv_palette.of_register_shortcut()
// Local variables
menu lm_item
// event ue_command_selected of inv_palette : run what the menu item runs
lm_item = inv_palette.of_loaded_item(/*key*/ as_key)
if IsValid(lm_item) then lm_item.TriggerEvent(Clicked!)
Words the search reads #
// Keywords are not displayed, but the search reads them :
// typing "pdf" finds the export even if the label never says it
inv_palette.of_add_command(/*key*/ "export", /*label*/ "Export", /*group*/ "File", /*hint*/ "PDF, CSV or xlsx", /*shortcut*/ "Ctrl+E", /*keywords*/ "pdf csv xlsx")
Choosing another chord #
// Ctrl+K already taken by your application ? Pick another one.
// of_register_shortcut hands it over, and the old one goes on its own.
inv_palette.is_shortcut = "ctrl+shift+p"
inv_palette.of_register_shortcut()
Anchoring it under a field #
// Anchored under an input field, in screen pixels
// The palette is brought back inside the screen if it overflowed
inv_palette.of_anchor_under(/*control*/ sle_1)
inv_palette.is_placeholder = "Type a command..."
inv_palette.of_open()
// Remove one command, empty the list, close the palette
inv_palette.of_remove_command(/*key*/ "print")
inv_palette.of_clear_commands()
inv_palette.of_close()
Best practices #
- Already have a PowerBuilder menu?
of_load_menu(this.MenuID)turns it into commands, and the menu stays the master. - Give every command the same identifier as in your menu: one function handles both, and the user gets exactly the same thing.
- Call
of_register_shortcut()when the window opens, not on the firstof_open: a palette that only answers its key after having been opened with the mouse is of no use. - Fill in the keywords: that is what separates a palette people use from one where nothing is ever found. Think of the words the user says, not yours.
- Show the action's own chord in
as_shortcut: the palette then becomes the way to learn them. - Only put immediate actions in it. A command that opens a settings dialog, yes; a command that needs three parameters, no.
- Remove the commands that no longer make sense rather than let them fail: a palette that offers the impossible loses trust in one go.