Building a LabVIEW Case Selector for Large JKI State Machines

David Krause15 min read
HMI ProgrammingOther ManufacturerTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

A JKI State Machine driving ATE sequencing grows to many states, and LabVIEW's native case selector becomes the bottleneck. The native control is a single pop-up list with no search, no grouping, and no persistence. The fix is a Right-Click Framework (RCF) plugin. The RCF is the JKI add-on that runs user-written VIs from a context menu on block diagram objects. The plugin reads the selected Case Structure's frame names, shows them in a floating tree, and sets the structure's visible frame when you pick one. The checks below follow the order in which such a plugin fails, from menu registration through packaging. Each check names the reading to take, what it means, and the next check.

Frame Navigation Cost in Large Case Structures

A Case Structure shows one frame at a time. Reaching a given frame means one of three things:

  • stepping with the increment/decrement arrows,
  • scrolling the selector pop-up,
  • typing into the selector.

With JKI-style string cases, the frame list also carries separator cases whose names contain -------. These are section headers such as ---------- UI ----------. They group the states logically, but the native selector shows them as flat entries. An external navigator solves both problems. It reads the frame names through VI Server, shows them in a control that scrolls and groups properly, and writes the chosen index back to the structure.

The display control you choose decides how usable the navigator is:

Display control Mouse-wheel scroll Resize with panel Grouping by separator Notes
1D array of strings No Poor No First prototype; needed a separate vertical scroll bar to move quickly
Table, one visible column Yes Yes No Works for any Case Structure
Listbox Yes Yes No Viable flat alternative
Tree control Yes Yes Yes Indents states under ------- headers; falls back to a flat list for non-JKI structures

Use the tree. Apply this indent rule while walking the frame names in order:

  • A name that contains ------- becomes a top-level header.
  • Every following name becomes a child of the most recent header.
  • A structure with no separator names produces a flat list with nothing indented.
// Tree population rule (pseudo-code)
parent = ""                       // top level
for i, name in FrameNames:
    if contains(name, "-------"):
        AddItem(parent="", tag=i, text=name)   // header, top level
        parent = tag_of(i)
    else:
        AddItem(parent=parent, tag=i, text=name) // child of last header, or top level if none yet

Store the frame index in each tree item's tag. Keep one rule from the start: a selection returns an index, never a name. Case names are not unique across structure types, and the property that changes the displayed frame takes an index.

Check 1: Plugin Registration in the RCF Menu

Reading: select a Case Structure, press the RCF Menu Activation Keys, and right-click. Look for the plugin entry in the RCF menu.

  1. Unzip the plugin into [LabVIEW]\resource\JKI\RCF\Plugins\CaseSelector. Expected: the plugin folder sits beside the other RCF plugins.
  2. Check the custom menu definition. A plugin without submenus uses the plain form CaseSelector : Case_Selector_Tag. Do not use the bracketed form >CaseSelector : My_Menu_Tag<, because the > and < markers are only needed for submenus. Expected: one flat entry named CaseSelector.
  3. Replace placeholder tags such as My_Menu_Tag with a meaningful tag such as Case_Selector_Tag. The tag is how the plugin tells which menu item fired, and placeholder tags collide once several plugins are installed. Expected: the tag appears in the plugin's menu-dispatch case and in no other plugin.

Outcomes: if the entry is missing, the folder path or menu file is wrong, so fix it and re-test. If the entry appears, go to Check 2.

Check 2: Selection Resolves to a Case Structure

Reading: the class of the first element in the RCF's array of selected objects.

The first prototype polled the block diagram and acted when the only selected object was a Case Structure. Polling is the wrong architecture for an editor plugin. It burns CPU in the development environment, reacts to selection changes the user never meant as commands, and competes with the RCF's own event handling.

The correct flow is event-driven:

  1. Take the first element of the selected-objects array that the RCF passes in.
  2. Test its class, using To More Specific Class or a class-name comparison, against Case Structure. If the test fails, exit without error. Expected: right-clicking a While Loop or a wire produces no window.
  3. If it is a Case Structure, read the frame names and pass them to the dialog sub-VI.
  4. The dialog returns the selected index to the RCF VI, which applies it (Check 4).

Outcomes: if the plugin fires on a non-Case-Structure selection, the class test is missing or runs on the wrong element. If the Case Structure resolves but nothing appears on screen, go to Check 3.

Check 3: Dialog Front Panel Opens and Returns

Reading: after you invoke the plugin once, try a second RCF plugin on any object.

The typical failure: the plugin runs once, and after that the RCF stops responding until LabVIEW restarts. The cause is a sub-VI that shows the list and waits in an Event Structure for a selection. When its front panel never opens, the user has nothing to click. The Event Structure waits forever, the calling RCF VI never returns, and the framework stays blocked.

  1. Open the dialog sub-VI's VI Properties > Window Appearance > Customize. Enable showing the front panel when called and closing it afterward, if it was originally closed. Expected: the panel appears on every call, not only when it was left open in the editor.
  2. Rebuild the distribution from the corrected source. A stale copy in the zip or .llb brings the hang back even after the source is fixed. Expected: a fresh install of the package does not hang.
  3. Test selection of the first list item on its own. An index-0 edge case is a known failure. Code that treats 0 as "no selection", or that fires only on a value change from the default of 0, never applies frame 0. Expected: selecting the first item shows frame 0.

Outcomes: if the RCF still hangs, the dialog blocks the caller by design, so go to Check 6 and launch it asynchronously. If the dialog returns an index, go to Check 4.

Check 4: Frame Brought Forward Without Flashing

Reading: watch the block diagram as a selection is applied.

The first implementation used ObjHighlite to bring the chosen case to the front. That call exists to draw the user's eye to an object, so it flashes the structure and leaves it selected when it finishes. Both side effects are wrong for a navigator that runs many times per editing session.

Write the index to the Case Structure's VisibleFrame property instead. VisibleFrame is the property that sets which frame the structure currently displays. Setting it switches the frame directly, with no highlight animation. Because the plugin no longer calls a highlight routine, the flash disappears.

// Apply selection (RCF VI)
CaseStructRef  = first element of RCF selection, cast to Case Structure
Index          = value returned by dialog sub-VI
Property Node (CaseStructRef): VisibleFrame <- Index

Outcomes: if the frame changes with no flash, go to Check 5. If the structure still flashes, a leftover highlight call remains in the apply path, so remove it.

Check 5: Click Events Map to a Named Tree Cell

Reading: click on non-item areas of the tree: the expander glyph [+]/[-], the scroll bar, and empty space below the last item.

A tree control raises mouse events for the whole control area. If any Mouse Up or Double-Click event counts as a frame selection, three false selections follow:

User action Wrong result Correction
Double-click on [+]/[-] Frame selected instead of section toggled Resolve the click with PointToRowColumn and act only on a named cell
Releasing the scroll bar thumb inside the tree Treated as a selection Ignore events whose coordinates do not resolve to an item
Single-click when double-click is required Nothing, so navigation is slow Use single-click once the hit test prevents false selections

PointToRowColumn is the tree/table method that converts a panel coordinate to a row, column, and "inside" flag. Once selections are filtered through it, you can move from double-click to single-click. Double-click was only there to keep expand/collapse usable.

Navigation keys are part of this check too. Arrow-key movement through the tree must also change VisibleFrame. Handle the tree's Value Change event, or key events, in addition to mouse events. If you skip this, the highlighted tree item and the displayed frame drift apart.

Panel polish belongs in this pass as well:

  • Use system-style controls instead of 3D controls.
  • Remove the tree's column header, which wastes vertical space.
  • Remove any OK button and close the window with the window's Close button, handled through the Panel Close? event.
  • Auto-hide the vertical scroll bar when all items fit.

Outcomes: if clicks on non-item areas no longer change frames, go to Check 6.

Check 6: Asynchronous, Floating, Reentrant Launch

Reading: with a CaseSelect window open, invoke another RCF plugin, then open a second CaseSelect window on a different Case Structure.

A modal dialog called as a normal sub-VI blocks the RCF until it closes. A navigator must stay open while you edit, so the launch model changes:

  • Floating window. Set the window behavior to Floating so it stays above the block diagram without taking focus from it.
  • Asynchronous run. Start the dialog with the VI.Run method and do not wait for it to finish. The RCF VI then returns immediately, and the dialog stays open after a selection instead of closing its front panel.
  • Reentrancy. A reentrant VI runs each call in its own data space. That is what allows two CaseSelect windows on two Case Structures at once. Set reentrancy on the target VI with the Execution.IsReentrant property. Then open the reference with the Options terminal of Open VI Reference set to 0x08, which prepares the reference for a reentrant run. Without 0x08, every open returns the same instance.
// Launch sequence (RCF VI)
Path    = path from Static VI Reference (see Check 7)
VIRef   = Open VI Reference(Path, Options = 0x08)
Set control values on VIRef (Case Structure reference, frame names)
Invoke Node VIRef: Run VI (Wait Until Done = FALSE)
// RCF VI returns; dialog continues independently

Asynchronous launch creates a lifetime problem. The dialog outlives the call, so it can also outlive the VI whose diagram it edits. Closing the edited VI leaves a floating panel that points at nothing. The event-driven design has no event for "owning VI closed", so a slow poll is justified here. Check the owning VI's front panel state through FP.State on a timeout case, and close the dialog when the owner closes. A useless panel floating around is worse than a low-rate poll.

Give each window a title that contains the owning VI name, and nothing else. With several windows open, the title is the only way to tell them apart.

Outcomes: if two windows open, each drives its own structure, and each closes with its owning VI, go to Check 7.

Check 7: Dynamic VI Survives Packaging

Reading: install the built package (for example, a VIPM package) on a clean LabVIEW installation and invoke the plugin.

A VI that is only called through Open VI Reference with a path is invisible to the linker. Build and package tools that follow static dependencies leave it out, or relocate it so the hard-coded path no longer resolves. The plugin then works on the development machine and fails once installed.

  1. Put the dynamically called VI inside a Static VI Reference node in the calling VI, and wire that node's output to Open VI Reference, or derive the path from it. Expected: the dynamic VI appears in the caller's hierarchy and dependency list.
  2. Open the caller. A broken dynamic VI now breaks the caller's run arrow immediately. With a path-based call, the error only shows up at run time. Expected: editing the dialog VI into a broken state shows at once in the RCF VI.
  3. For reentrant use through a static reference, set Execution.IsReentrant on the referenced VI and open it with option 0x08 as in Check 6. Expected: multiple independent instances still launch from the static reference.

Sharing the launch code as a VI Snippet image (LabVIEW 2009 and later) does not carry the statically referenced VI with it. The snippet shows the Static VI Reference but does not embed the VI inside it. Distribute the full package for anything with dynamic dependencies.

Outcomes: if the clean install works, go to Check 8 when you support LabVIEW 8.2. Otherwise go to the editing-operations section.

Check 8: LabVIEW 8.2 Back-Port Breakage

Reading: open the down-saved VIs in LabVIEW 8.2 and 8.2.1 and inspect the run arrow and error list.

The RCF supports LabVIEW from 8.2.0, so a plugin developed in 8.6 needs an 8.2 build. Saving for a previous version keeps the file format but does not remove properties and methods that the older version lacks. Those nodes load broken.

Symptom in 8.2 Cause Correction
Delete-frame call broken The Case Structure delete method used in 8.6 is not implemented in 8.2 Replace it with an equivalent path available in 8.2, and test deletion on 8.2 directly
VI broken at the timeout-frame property node The property used there has a different name in 8.2 than in 8.6 Read the owning VI's front panel state through FP.State, which exists in both
Tree context-menu element breaks the reentrant VI The runtime-menu configuration used in the 8.6 build does not resolve in the 8.2 reentrant VI Removing the element restores the run arrow but loses the tree menu; rebuild the menu with 8.2-compatible calls or give up that feature on 8.2
Drag-drop appears to swap wrong frames (e.g., an exit frame and an Event Structure frame) The tree was not refreshed after a reorder, so displayed indices were stale Re-read frame names and rebuild the tree after every structural edit

Test on the target version, not on 8.6 with a down-save. Each of these breaks shows up only in the older runtime.

Editing Operations and Tree-to-Structure Sync

Once navigation works, the tree becomes an editing surface for the Case Structure. Each operation below changes frame indices, so each must be followed by a re-read of the frame names and a tree rebuild. Stale indices are why an earlier build appeared to swap the wrong frames.

Operation Input Behavior and constraint
Insert state Insert key or context menu Adds a frame. A plain insert leaves tunnels unwired, so prefer the duplicate forms below
Delete state Delete key or context menu Removes the frame; see the 8.2 method gap in Check 8
Reorder Drag/drop in tree Works without a confirmation prompt. Undo for this operation was not implemented, so save before a large reorder
Duplicate Ctrl+drag/drop, or a "Duplicate Case Here" menu item Copies the frame with its wiring (error, application data, queue), which is faster than wiring a new frame by hand
Collapse/expand all Tree context menu Sections open expanded on each call; scroll position persists per Case Structure

Dragging a section header moves the whole section. That is tree-control behavior, not plugin logic: children follow their parent item. It is the right outcome for JKI grouping, but the new frame order comes from the tree's post-drop order. Rebuild the frame order from the tree traversal, not from the dragged item alone.

Duplicating a state carries a naming trap. A duplicate needs a unique case name. Pre-fill the prompt with the source name's prefix, so that duplicating Macro: Initialize offers Macro:. That keeps the JKI naming convention and avoids a duplicate-name error on commit. For a new empty state, duplicate a separator case such as ---------- UI ---------- and rename it, because separators already carry the pass-through wiring.

Several features are worth adding:

  • An alphabetical sort that keeps separators and their groups intact.
  • A search combo box that filters states as you type and shows the closest match.
  • Bundled parse state queue.vi and add state to queue.vi in the plugin library, for state machines built in a different style.

Event Structures are out of scope for this approach. Their scripting interface does not expose enough to list and reorder event cases the same way.

Be careful with Ctrl+drag on diagrams that contain linked tunnels. LabVIEW has crashed when linked tunnels were Ctrl+dragged. Save before duplicating frames that use them.

Run the final verification in this order on a JKI State Machine that contains separator cases:

  1. Right-click the Case Structure through the RCF and choose CaseSelector. Expected: a floating, resizable window titled with the VI name, with states indented under each ------- header.
  2. Single-click the first item, then an item deep in the list. Expected: each frame comes to the front with no flash, including frame 0.
  3. Click the [+]/[-] glyph and drag the scroll bar thumb inside the tree. Expected: sections toggle and the list scrolls, and VisibleFrame does not change.
  4. Scroll to the bottom, then shrink and regrow the window. Expected: the top items can still be reached. A resize after scrolling must not strand the first cases above the visible area.
  5. Move through the tree with the arrow keys. Expected: the displayed frame follows the highlighted item.
  6. Invoke any other RCF plugin with the window still open. Expected: the RCF responds at once.
  7. Open a second CaseSelect window on another Case Structure. Expected: two independent windows, each driving only its own structure.
  8. Drag-reorder a state, then Ctrl+drag to duplicate one. Expected: the tree rebuilds and every item still selects the frame whose name it shows.
  9. Close the VI being edited. Expected: its CaseSelect window closes on the next timeout poll of FP.State.

FAQ

How do I bring a LabVIEW Case Structure frame to the front without it flashing?

Write the frame index to the Case Structure's VisibleFrame property through a property node on its reference. Do not call ObjHighlite, which exists to flash and select the object.

How do I make an RCF plugin window stay open without blocking the Right-Click Framework?

Set the dialog window to Floating and start it with the VI.Run method without waiting for it to finish. For multiple windows, set Execution.IsReentrant and open the reference with the Open VI Reference option 0x08. Poll the owning VI's FP.State on a timeout case so the dialog closes when that VI closes.

How do I include a dynamically called VI in a LabVIEW package build?

Place the dynamic VI in a Static VI Reference node in the caller and derive the Open VI Reference path from it. The VI then enters the dependency list and gets packaged, and a broken dynamic VI breaks the caller's run arrow at edit time instead of failing at run time.

Back to blog