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_nouveau()
case "open"; of_ouvrir()
case "save"; of_enregistrer()
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:
inv_palette.ipo_owner = this // the window the palette belongs to
inv_palette.of_register_shortcut() // the palette has to answer its key
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
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, and the palette's events are raised on it. Set it before of_open — it is the only wiring to do |
ipo_receiver | powerobject | — | Optional, legacy: a separate visual object on which to deliver the events, instead of ipo_owner. 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 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_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 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 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 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.