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 #
// Once, at startup : your application's actions
inv_palette.ipo_owner = this
inv_palette.of_add_command(/*key*/ "new", /*label*/ "N", /*group*/ "F")
inv_palette.of_add_command(/*key*/ "open", /*label*/ "O", /*group*/ "F")
inv_palette.of_add_command(/*key*/ "save", /*label*/ "S", /*group*/ "F")
inv_palette.of_register_shortcut()
// event ue_command_selected : (string as_key)
choose case as_key
case "new"; of_new()
case "open"; of_open()
case "save"; of_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: set ipo_owner to your window, and the palette drains its own events and raises them on that object.
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 on
ipo_owner— no receiver, no message to map, no timer.
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, its chord answers in that window, and the palette's events are raised on it. Set it before of_open — it is the only wiring to do. Left unset, the chord answers in every window of the application. 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 separate visual object on which to deliver the events, instead of ipo_owner — it is also the only window whose pbm_custom02 is rung at each event. Leave it empty — the palette now delivers its events on its own via ipo_owner |
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, and recency breaks ties when filtering — it never overrules the match. 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. 0 switches it off: an application whose users would rather see their groups untouched can say so. 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 |
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 |
of_insert_command (string as_key, string as_label, string as_group, integer ai_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_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 | |
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. 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 | |
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 | |
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 | |
of_process_events ( ) | Drains the queued events and raises them on ipo_owner. The component calls it itself as long as the palette lives: you normally do not have to | |
of_reset ( ) | 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 |
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 |
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 |
Examples #
Feeding the palette from your menu #
// 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*/ "E", /*group*/ "F", /*hint*/ "H", /*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 = "P"
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 #
- 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.