Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

title: "Display Construction — Canvas, Dashboard, Navigation & Symbols" tags: [display, canvas, dashboard, symbol, navigation, hmi, ui, layout] description: "Build displays from scratch: Canvas and Dashboard layouts, symbol placement, asset navigation, CodeBehind, and ActionDynamic click handling" version: "1.0" author: "Tatsoft"

...

What This Skill Does

Build complete FrameworX displays: choose between Canvas and Dashboard layouts, place controls and symbols with proper tag binding, wire up ActionDynamic for navigation, configure CodeBehind for display lifecycle events, and set up asset-driven navigation patterns.

When to Use This Skill

Use when:

  • Building a new display from scratch
  • User asks for a dashboard, HMI screen, or operator interface
  • Need to place symbols with tag bindings
  • Need navigation between displays
  • Need asset-driven dynamic displays

Do NOT use when:

  • Just modifying text or properties on an existing display (use get_objects + write_objects)
  • Need only trend charts or alarm viewers (fetch element schemas via list_elements)

Prerequisites

  • Solution open with tags created (Pillar 1 complete)
  • For symbol placement: know which tags to bind

MCP Tools and Tables

CategoryItems
Toolsget_table_schema, write_objects, get_objects, list_elements, list_dynamics
TablesDisplaysList, DisplaysSymbols, DisplaysLayouts

Critical: Use DisplaysList for creating and editing displays. DisplaysDraw is the Designer visual editor UI — NOT a writable table.

Implementation Steps

Step 1: Choose Panel Type

Fetch the display schema and decide Canvas vs Dashboard:

get_table_schema('DisplaysList')
list_elements('Canvas') or list_elements('Dashboard')

Canvas — absolute positioning (Left/Top/Width/Height). Use for:

  • Process diagrams with precise element placement
  • Traditional HMI screens with P&ID-style layouts
  • Supports Shapes (drawing primitives), Controls, Symbols, IndustrialIcons

Dashboard — responsive grid (Columns/Rows/Cells). Use for:

  • Data-centric monitoring screens
  • Responsive layouts that adapt to screen size
  • Supports Controls and Symbols only (no Shapes)

PanelType property is REQUIRED — omitting it silently defaults to Canvas, which causes confusing results if you intended a Dashboard.

Step 2: Place Elements

Query element schemas for available controls and their properties:

list_elements('TextBlock')           -- specific control schema
list_elements('CircularGauge')       -- specific control schema
list_elements('Shapes')              -- all drawing primitives (Canvas only)
list_elements('Interaction')         -- buttons, inputs, toggles
list_elements('Charts')              -- TrendChart, BarChart, PieChart
list_elements('Gauges')              -- CircularGauge, LinearGauge, BulletGauge
list_elements('Symbol/HMI/Labels')   -- browse symbol library folder

Batch lookups with comma-separated names:

list_elements('TrendChart,CircularGauge,TextBlock')

Element Positioning

Canvas elements require Left, Top, Width, Height:

{
  "Type": "TextBlock",
  "Text": "Tank Level",
  "Left": 100, "Top": 50, "Width": 200, "Height": 30
}

Dashboard elements use Column, Row, ColumnSpan, RowSpan:

{
  "Type": "TextBlock",
  "Text": "Tank Level",
  "Column": 0, "Row": 0, "ColumnSpan": 2, "RowSpan": 1
}

Data Binding

Bind element properties to runtime tags using @Tag. prefix:

{
  "Type": "TextBlock",
  "Text": "@Tag.Plant/Tank1/Level.Value",
  "Foreground": "#FFFFFF"
}

Step 3: Place Symbols

Browse available symbols:

list_elements('Symbol')              -- all symbol categories
list_elements('Symbol/HMI')         -- HMI symbol library
list_elements('Symbol/HMI/Equipment') -- equipment symbols
list_elements('Library')             -- all library folders

Symbol placement pattern:

{
  "Type": "Symbol",
  "SymbolName": "HMI/Equipment/Pump",
  "Left": 200, "Top": 300, "Width": 80, "Height": 80,
  "SymbolLabels": [
    { "Key": "State", "Value": "@Tag.Plant/Pump1.Running" },
    { "Key": "Speed", "Value": "@Tag.Plant/Pump1.Speed" }
  ]
}

CRITICAL: Use @Tag. bindings in SymbolLabels. NEVER use @Label. — that prefix is only for symbol internal definitions (in DisplaysSymbols).

WizardSymbols — always available, no import needed: TANK, VALVE, PUMP, MOTOR, BLOWER. These provide easy customization through SymbolLabels for common industrial equipment.

Library symbols (~1,600 available) auto-import when referenced by SymbolName. No manual import step needed — just reference the path.

Step 4: Add Click Actions and Dynamics

For button/navigation behavior:

list_dynamics('ActionDynamic')

ActionDynamic supports two formats:

Simple format — single action, auto-mapped to MouseLeftButtonDown:

{
  "Type": "Button",
  "Text": "Go to Details",
  "ActionDynamic": {
    "Type": "NavigateToDisplay",
    "DisplayName": "DetailPage"
  }
}

Nested format — multi-event, explicit mouse event mapping:

{
  "ActionDynamic": [
    {
      "Event": "MouseLeftButtonDown",
      "Actions": [
        { "Type": "NavigateToDisplay", "DisplayName": "DetailPage" }
      ]
    }
  ]
}

Common pattern: navigation button = ActionDynamic + ShineDynamic for visual click feedback.

For all available dynamics:

list_dynamics()     -- lists all dynamic types

Step 5: Write the Display

Displays are document objects — full replacement on write.

Creating a new display:

{
  "table_type": "DisplaysList",
  "data": [{
    "ObjectName": "OverviewPage",
    "PanelType": "Dashboard",
    "Columns": 4,
    "Rows": 3,
    "Content": [
      {
        "Type": "TextBlock",
        "Text": "Plant Overview",
        "Column": 0, "Row": 0, "ColumnSpan": 4, "FontSize": 24
      },
      {
        "Type": "CircularGauge",
        "Value": "@Tag.Plant/Tank1/Level.Value",
        "Column": 0, "Row": 1
      }
    ]
  }]
}

Modifying an existing display — read first, modify, write back:

get_objects('DisplaysList', names=['MainPage'])

Then modify the content and write the full object.

MainPage is predefined and MCP-labeled in new solutions. Write main content directly into it — no need to create a new display for the landing screen.

Step 6: Configure Layouts

Layout regions: Header, Footer, Menu, Submenu, Content.

get_table_schema('DisplaysLayouts')
get_objects('DisplaysLayouts', names=['Startup'])

The Startup layout defines which display loads into the Content region (typically MainPage). Only modify if you need:

  • A different startup page
  • Custom header/footer regions
  • Navigation menus

Layouts are document objects — read first, modify, write back.

Dependencies: DisplaysSymbols → DisplaysList → DisplaysLayouts

Step 7: CodeBehind (Optional)

CodeBehind is CLIENT-SIDE C# code embedded in each display for lifecycle events and operator interaction:

  • DisplayOpening() — runs once when the display loads
  • DisplayIsOpen() — runs cyclically while displayed
  • DisplayClosing() — runs once when the display closes

Use for: operator UI event handlers, local display state, client-side calculations. Not part of the Script module — embedded in each display's content.

See the Scripts and Expressions skill for full CodeBehind guidance.

Step 8: Asset Navigation Pattern (Advanced)

Common pattern for plant-wide navigation with dynamic content:

Architecture:

  • Layout with Header + AssetTree (left menu) + Content region
  • User selects an asset → Client.Context updates → content displays react

Static binding (fixed tag path):

@Tag.Area1/Line1/State

Dynamic binding (resolves based on selected asset):

Asset(Client.Context.AssetPath + "State1")

This allows a single display template to show data for whichever asset the operator selects in the navigation tree.

Setting Up Asset Navigation

  1. Create tags organized by consistent asset paths (e.g., Plant/Line1/Motor1, Plant/Line2/Motor1 with same member names)
  2. Modify the Startup layout to include an AssetTree region
  3. Use Asset() syntax in display bindings for dynamic resolution
  4. Use browse_runtime_properties('Client') at design time to explore the Client namespace and Context properties

Step 9: Theme and Color Management

When setting custom colors on elements, be aware that theme overrides can silently replace your custom values at runtime.

Pattern — set color AND clear theme:

{
  "Fill": "#FF3498DB",
  "FillTheme": ""
}

If you only set Fill without clearing FillTheme, the theme engine may override your color. Always clear the corresponding theme property when setting custom colors.

Common Pitfalls

MistakeWhy It HappensHow to Avoid
Using DisplaysDraw as table_typeConfused with DisplaysListDisplaysDraw is the visual editor UI, not a writable table
Omitting PanelTypeDefaults silently to CanvasAlways set PanelType explicitly
Using @Label. in display elementsConfused with symbol definitions@Label. is only for DisplaysSymbols internals
Screenshots for self-validationHabit from other toolsTrust write_objects success. User sees live updates
Setting colors without clearing themeTheme overrides custom colorsSet value AND clear theme: {Fill: '#FF3498DB', FillTheme: ''}
Sending partial display contentForget it's a document objectAlways read-modify-write for displays
Canvas positioning in DashboardMixing layout paradigmsCanvas uses Left/Top; Dashboard uses Column/Row
Referencing symbols with wrong pathIncomplete library pathBrowse with list_elements('Symbol/HMI') to get exact paths

Quick Reference

Display ActionTool Call
Get display schemaget_table_schema('DisplaysList')
Get element schemalist_elements('ElementName')
Browse symbolslist_elements('Symbol/HMI') or list_elements('Library')
Get dynamics schemalist_dynamics('ActionDynamic')
Read existing displayget_objects('DisplaysList', names=['PageName'])
Write displaywrite_objects('DisplaysList', data=[...])
Navigate Designer to displaydesigner_action('navigate', 'Displays', 'Draw', 'PageName')