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.ipo_receiver = 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_nouveau()
case "open"; of_ouvrir()
case "save"; of_enregistrer()
end choose
The wiring: the receiver #
A non-visual object has no window handle: Windows has nobody to hand the palette's messages to. That is what ipo_receiver is for — a visual object, your window for instance, that listens and drains.
Three lines, once, when the window opens:
inv_palette.ipo_owner = this // the window the palette belongs to
inv_palette.ipo_receiver = this // the one that will receive the events
inv_palette.of_register_shortcut() // the palette has to answer its key
Then, on the receiver, the event that drains:
// event ue_palette_msg pbm_custom02
inv_palette.of_process_events()
Without that drain the palette opens and works, but nothing comes back to you: not the choice, not the opening, not the closing.
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
Two components asking for the same chord: the one holding the focus wins, otherwise the first registered. 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: convert before filling il_x / il_y.
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. Set it before of_open |
ipo_receiver | powerobject | — | The visual object that receives the events. It declares event xxx pbm_custom02 and calls of_process_events there |
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 applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
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 keywords the search reads without showing them. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_remove_command (string as_key) | Removes one action; the others stay. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
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_clear_commands ( ) | Empties the palette. Returns 0 once applied, -2 when the component is not created |
of_count ( ) → integer | How many commands the palette holds |
of_keys_at ( integer ai_index ) → string | The id of the command at rank ai_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: declaring a second one under an id already taken is how a list grows a twin |
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 open, -1 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 |
of_close ( ) | Closes it. 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 window: 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 removed it |
of_process_events ( ) | Drains the queued events and raises them on this object. Call it from ipo_receiver's pbm_custom02 handler — it is the only way back |
of_reset ( ) | Empties the commands and returns the properties to their defaults. ipo_owner and ipo_receiver are left alone: they are the wiring, not the content. Returns 0 once applied, -2 when the component is not created |
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) |
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(). You may also refuse |
ue_opened ( ) | The palette is on screen — through of_open |
ue_closed ( ) | It has just closed, whether or not something was picked |
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" |
| 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 : PB counts in PBU, the DLL in pixels
// The palette is brought back inside the screen if it overflowed
inv_palette.is_position = inv_palette.POSITION_ABSOLUTE
inv_palette.il_x = UnitsToPixels(sle_1.x, XUnitsToPixels!)
inv_palette.il_y = UnitsToPixels(sle_1.y + sle_1.height, YUnitsToPixels!)
inv_palette.is_placeholder = "P"
inv_palette.of_open()
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.