PBToolboxAI v2 ← Site

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 #

Objectn_pbt_commandpalette — non-visual: nothing to drop on the window
Used forMaking every action of the application reachable from the keyboard, in three letters
ReturnNon-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.

ConstantWhere
POSITION_WINDOW_CENTERCentred on ipo_owner — the default, and what the eye expects
POSITION_SCREEN_CENTERCentred on the screen, whatever the window
POSITION_ABSOLUTEAt 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 #

PropertyTypeDefaultRole
ipo_ownerpowerobject—The window the palette belongs to: it owns the popup window and anchors it. Set it before of_open
ipo_receiverpowerobject—The visual object that receives the events. It declares event xxx pbm_custom02 and calls of_process_events there
is_shortcutstring"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_positionstringwindow-centerWhere the window lands (POSITION_* constants)
il_x · il_ylong0Position in screen pixels, read by POSITION_ABSOLUTE only
is_placeholderstring""Grey text shown in the search box while nothing has been typed
is_recentstring""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_recentlong8How 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 #

MethodRole
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_commandThe 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 ( ) → integerHow many commands the palette holds
of_keys_at ( integer ai_index ) → stringThe 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 ) → booleanDoes 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.

PropertyTypeDefaultRole
is_labelstring—The text of the row
is_shortcutstring""The chord shown at the right of the row, and honoured while the palette is open (Ctrl+Shift+S)
ib_enabledbooleantrueCommand greyed: visible, searchable, and inert — neither click, Enter nor its chord
ib_visiblebooleantrueCommand hidden: out of the list and of the chords, without being removed; it comes back as it was

Events #

EventRaised 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 #

KeyEffect
The chord of is_shortcutTells your code through ue_shortcut; that is what opens it
TypingFilters as you type: the letters need not follow each other, nwf finds "New file"
Up / down arrowsMove the selection through the list
EnterPicks the selected action (ue_command_selected)
The chord shown on a rowRuns that command, without having to select it first
EscapeCloses 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 #


← Component reference · Guide contents