Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
---
title: "Display Construction  Canvas, Dashboard, Dynamics, Symbols & CodeBehind"
tags: [display, canvas, dashboard, symbol, navigation, hmi, ui, layout, dynamics, codebehind]
description: "Build displays from scratch: Canvas and Dashboard layouts, zone-based Canvas positioning, symbol placement with SymbolLabel binding, visual dynamics (color, visibility, rotation), ActionDynamic click handling, header navigation for multi-page solutions, CodeBehind for display logic, and asset-driven navigation"
version: "

...

4.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 visual dynamics (color, visibility, rotation based on tag values), configure ActionDynamic for navigation, write CodeBehind for display place page navigation buttons in the persistent Header display, write CodeBehind for display lifecycle events, and set up asset-driven navigation patterns.

...

list_dynamics() with no parameter is the door to the entire dynamics system — it lists every available dynamic type organized by category with minimal examples.

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: Build a Canvas Display

Canvas uses absolute positioning with Elements array. Each element needs Left, Top, Width, Height.

2a: Query Element Schemas

...

CRITICAL: Display Write Format

The correct format for write_objects on DisplaysList uses top-level properties directly. The identifier field is Name.

Correct — flat format, everything at top level:

{
  "Name": "MyDisplay",
  "PanelType": "Canvas",
  "DisplayMode": "Page",
  "Size": "1366 x 720",
  "Elements": [
    {
      "Type": "Rectangle",
      "Left": 100, "Top": 100, "Width": 200, "Height": 150
    }
  ]
}

...

Implementation Steps

Step 1: Choose Panel Type

Fetch the display schema and decide Canvas vs Dashboard:

get_table_schema('DisplaysListlist_elements('TextBlock,CircularGauge,Rectangle')
list_elements('ShapesCanvas')              -- all drawing primitives
or list_elements('InteractionDashboard')         -- buttons, inputs, toggles
list_elements('Charts')              -- TrendChart, BarChart, PieChart
list_elements('Gauges')              -- CircularGauge, LinearGauge, BulletGauge

Batch lookups with comma-separated names:

list_elements('TrendChart,CircularGauge,TextBlock')

2b: Canvas Construction Example

A concrete Canvas display with shapes, text, a symbol, and a dynamic:

{
  "table_type": "DisplaysList",
  "data": [{
    "ObjectName": "ProcessView",
    "PanelType": "Canvas",
    "Size": "1366 x 728",
    "OnResize": "StretchFill",
    "Elements": [
      {
        "Type": "TextBlock",
        "Text": "Process Overview",
        "Left": 50, "Top": 20, "Width": 300, "Height": 30,
        "FontSize": 22
      },
      {
        "Type": "Rectangle",
        "Left": 50, "Top": 80, "Width": 200, "Height": 150,
        "Dynamics": [
          {
            "Type": "FillColorDynamic",
            "LinkedValue": "@Tag.Plant/Tank1/ValveOpen",
            "ChangeColorItems": {
              "Type": "ColorChangeList",
              "Children": [
                { "Type": "ChangeColorItem", "ChangeLimit": 0, "LimitColor": "#FF808080" },
                { "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FF4CAF50" }
              ]
            }
          }
        ]
      },
      {
        "Type": "TextBlock",
        "LinkedValue": "{@Tag.Plant/Tank1/Level.Value}",
        "Left": 100, "Top": 160, "Width": 100, "Height": 25
      },
      {
        "Type": "Symbol",
        "SymbolName": "Wizard/PUMP",
        "Left": 300, "Top": 120, "Width": 80, "Height": 80,
        "SymbolLabels": [
          { "Type": "SymbolLabel", "Key": "State", "LabelName": "State", "LabelValue": "@Tag.Plant/Pump1/Running.Value", "FieldType": "Expression" }
        ]
      },
      {
        "Type": "CircularGauge",
        "LinkedValue": "@Tag.Plant/Tank1/Pressure.Value",
        "Left": 450, "Top": 80, "Width": 180, "Height": 160,
        "Minimum": 0, "Maximum": 100
      }
    ]
  }]
}

Key Canvas rules:

  • Every element needs Left, Top (position) and Width, Height (size)
  • Shapes (Rectangle, Ellipse, Polyline, Polygon, Path) are Canvas-only
  • Use Size: "1366 x 728" for standard content area
  • OnResize: "StretchFill" scales proportionally to fill the layout region

Step 3: Build a Dashboard Display

Dashboard uses a grid with DashboardDisplay (columns/rows definition) and Cells array.

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.

Decision guide: If the display is primarily data monitoring (gauges, trends, grids), prefer Dashboard — it handles spacing automatically. Use Canvas only when process flow diagrams, P&ID schematics, or custom shapes/lines are needed. For mixed needs: Dashboard for the main monitoring page, Canvas for process overview pages.

...

Step 2: Canvas Zone-Based Layout (CRITICAL — READ BEFORE BUILDING ANY CANVAS)

AI agents consistently produce poorly laid out Canvas displays when placing elements with arbitrary coordinates. Always use the zone-based approach described here.

Problem: What Goes Wrong Without Zones

  • Elements too small and tightly packed in one corner
  • Bottom 50–60% of the canvas left empty
  • Labels disconnected from their values
  • No visual grouping — everything floats independently
  • Symbols too small to read at operator distance
  • Value and Unit in separate TextBlocks with awkward spacing

Core Principle: Think in Zones, Not Elements

NEVER start by placing individual elements. Instead:

  1. Divide the canvas into zones (rectangular regions for each process section)
  2. Size each zone based on content complexity
  3. Place elements within zones using local coordinates relative to zone origin
  4. Connect zones with flow indicators

Step 2a: Divide Canvas into Zones

Before writing ANY element, mentally divide the 1366×720 canvas into rectangular zones. Each zone represents a process area, equipment group, or information panel.

Standard zone layout for a process overview (1366×720):

PAGE TITLE (x:0, y:0, w:1366, h:50) ACTION BTNs

Zone 1

h:300

Zone 2Zone 2Zone 2Zone 2Zone 2Zone 2
BOTTOM PANEL: Trend, Alarms, KPIs, or ML Status (h:300+)
Note: The title bar is for the page title and optional in-page action buttons (start/stop, acknowledge). Page-to-page navigation buttons go in the Header display, not here (see Step 10b).

Zone sizing rules:

  • Title bar: full width, 50px tall
  • Process zones: divide remaining width equally among process stages
  • Each zone minimum: 160px wide × 280px tall
  • Bottom panel: full width, remaining height
  • Margin between zones: 15–20px
  • Padding inside zones: 10–15px from zone border to content

Step 2b: Calculate Zone Coordinates

For N process stages on a 1366×720 canvas:

Available width  = 1366 - 20 (left/right margin 10px each)  = 1346
Available height = 720 - 50 (title) - 10 (top margin)       = 760

Zone width  = (Available width - (N-1) × 15) / N
Zone height = 320  (for process row)
Zone startY = 60   (below title)

Zone[i].Left = 20 + i × (zoneWidth + 15)
Zone[i].Top  = 60
Zone[i].Width = zoneWidth
Zone[i].Height = 320

Bottom panel:
  Left = 20, Top = 400, Width = 1326, Height = 658 - 320 - 20 = 318

Example: 6 process stages

Zone width = (1326 - 5×15) / 6 = 208px each

Zone 0: Left=20,   Top=60, W=208, H=320
Zone 1: Left=243,  Top=60, W=208, H=320
Zone 2: Left=466,  Top=60, W=208, H=320
Zone 3: Left=689,  Top=60, W=208, H=320
Zone 4: Left=912,  Top=60, W=208, H=320
Zone 5: Left=1135, Top=60, W=208, H=320

Bottom: Left=20, Top=400, W=1326, H=308

Step 2c: Zone Background Rectangles (REQUIRED)

Every process zone MUST have a background Rectangle for visual grouping. Use themed colors — never hardcode colors for zone backgrounds.

Standard zone background — use PanelBackground theme:

{
  "Type": "Rectangle",
  "Left": 20, "Top": 60, "Width": 208, "Height": 320,
  "FillTheme": "PanelBackground",
  "Stroke": "#FF909090", "StrokeThickness": 1
}

CRITICAL theming rules for zone backgrounds:

  • Use FillTheme: "PanelBackground" — this respects Light/Dark theme switching automatically
  • Do NOT hardcode fill colors like "Fill": "#0A000000" — these break in Dark theme
  • Only set explicit Fill + FillTheme: "" when you need process-specific meaning (e.g., water blue for a tank zone)

Zone backgrounds must be placed FIRST in the Elements array so they render behind all other content.

Step 2d: Standard Zone Internal Layout

Every process zone follows the same internal layout pattern. Coordinates are offsets from the zone's Left/Top origin:

Panel
borderColor#C8C8C8
bgColor#FAFAFA
borderWidth1
borderStylesolid
titleZone Title

(FontSize 15-16) Background: PanelBackground Theme, y+10

Symbol

(Pumpt, Motor,..

80×80 to 100×100,  centered horizontally, y+40

Label: Value Unit (FontSize 14) y+150
Label: Value Unit y+176
Label: Value Unit y+202
Status indicator (optional) y+240



Symbol centering formula:

Symbol.Left = Zone.Left + (Zone.Width - Symbol.Width) / 2

Step 2e: Value + Unit Text Pattern

NEVER put value and unit in separate TextBlocks side by side. Instead, use ONE TextBlock with composite LinkedValue:

{
  "Type": "TextBlock",
  "LinkedValue": "Flow: {@Tag.Plant/Intake/FIT_1001} GPM",
  "Left": 30, "Top": 150, "Width": 170, "Height": 24,
  "FontSize": 14
}

Patterns:

  • "Flow: {@Tag.X} GPM" — label + value + unit
  • "{@Tag.X} NTU" — value + unit only
  • "pH: {@Tag.X}" — label + value (no unit)

Step 2f: Flow Arrows Between Zones

Show process flow direction between zones using TextBlock arrows:

{
  "Type": "TextBlock",
  "Text": "→",
  "Left": 228, "Top": 190, "Width": 15, "Height": 30,
  "FontSize": 20
}

Arrow placement: horizontally centered in the gap between zones, vertically aligned with the symbol center.

Step 2g: Bottom Panel Patterns

The bottom panel (below process zones) should contain ONE of:

Option A: Trend + KPI sidebar

Bottom Panel
TrendChart (w:700, h:280)

KPI Column
Value1: ###
Value2: ###

Option B: Trend + Alarm split
Bottom Panel
TrendChart (w:660, h:280)

AlarmViewer

(w:640, h:280)


Option C: Full-width trend with status bar

Bottom Panel  (h:30)
Status: Score: 0.023 Confidence: 0.98

AlarmViewer

(w:640, h:280)

Bottom panel sections should also use PanelBackground theme background rectangles.

Step 2 Summary: Element Order in Elements Array

  1. Zone background Rectangles (renders first = behind everything)
  2. Title TextBlock
  3. In-page action buttons (start/stop, acknowledge — NOT page navigation)
  4. Zone titles
  5. Symbols (centered in each zone)
  6. Value TextBlocks (below symbols in each zone)
  7. Flow arrows between zones
  8. Bottom panel background Rectangle(s)
  9. Bottom panel content (TrendChart, AlarmViewer, etc.)
  10. Dynamic indicators and status elements

...

Step 3: Minimum Element Sizes (Non-Negotiable for Canvas)

ElementMinimum SizeRecommended
Wizard Symbol70×7080×80 to 100×100
Library Symbol60×6080×80
Section TitleFontSize 14FontSize 15–16
Value TextFontSize 13FontSize 14
Unit LabelFontSize 11FontSize 12
Gauge (Circular)150×150180×180
TrendChart300×180500×200
AlarmViewer400×150600×200
Button100×35130×40

...

Step 4: Build a Dashboard Display

Dashboard uses a grid with DashboardDisplay (columns/rows definition) and Cells array.

list_elements('Dashboard')
{
  "table_type": "DisplaysList",
  "data": [{
    "Name": "MainPage",
    "PanelType": "Dashboard",
    "DashboardDisplay": {
      "Columns": ["*", "*", "*"],
      "Rows": ["*", "*"]
    },
    "Cells": [
      {
        "Row": 0, "Col": 0,
        "Cell": { "HeaderLink": "Tank 1 Level" },
        "Content": {
          "Type": "CircularGauge",
          "LinkedValue": "@Tag.Plant/Tank1/Level.Value",
          "Minimum": 0, "Maximum": 100
        }
      },
      {
        "Row": 0, "Col": 1,
        "Cell": { "HeaderLink": "Tank 1 Temp" },
        "Content": {
          "Type": "CircularGauge",
          "LinkedValue": "@Tag.Plant/Tank1/Temperature.Value",
          "Minimum": 0, "Maximum": 100
        }
      },
      {
        "Row": 0, "Col": 2,
        "Cell": { "HeaderLink": "Trend" },
        "Content": {
          "Type": "TrendChart",
          "Duration": "5m",
          "Pens": {
            "Type": "TrendPenList",
            "Children": [
              { "Type": "TrendPen", "LinkedValue": "@Tag.Plant/Tank1/Level", "PenLabel": "Level", "Stroke": "#FF2196F3", "Auto": true }
            ]
          }
        }
      },
      {
        "Row": 1, "Col": 0, "ColSpan": 3,
        "Cell": { "HeaderLink": "Equipment Status" },
        "Content": {
          "Type": "Symbol",
          "SymbolName": "Wizard/TANK",
          "SymbolLabels": [
            { "Type": "SymbolLabel", "Key": "Value", "LabelName": "Value", "LabelValue": "@Tag.Plant/Tank1/Level.Value", "FieldType": "Expression" }
          ]
        }
      }
    ]
  }]
}

Key Dashboard rules:

  • DashboardDisplay defines the grid: Columns and Rows arrays (use "*" for proportional sizing)
  • Each Cell references Row/Col (0-based) and contains a Content element
  • No Left/Top needed — elements auto-fill their cell
  • ColSpan/RowSpan for multi-cell elements
  • Cell.HeaderLink adds a header label to the cell
  • For all controls and symbols verify and change as needed the Height and Width it will be used
  • For Symbols modify the height and width proportional to original size, so it's resized nicely
  • You never set Width or Height on the Dashboard itself, just on cells/content
  • if User request a dashboard not giving information to allow you to define in number cells, create a 3 x 2 grid.
  • If the Controls or Symbols to show are not defined in the context, use Gauges on first row;  TrendChart, AlarmViewer, and DataGrid on second row.

...

Step 5: 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
list_elements('Wizard')              -- Wizard symbol schema

Symbol Placement with SymbolLabel Binding

Symbols are reusable components with parameterized inputs. When you place a symbol, you use SymbolLabels to connect the symbol's internal @Label. parameters to actual @Tag. values.

  • Inside a symbol definition (DisplaysSymbols), internal bindings use @Label.State, @Label.Speed, etc.
  • When placing a symbol in a display (DisplaysList), you provide SymbolLabels that map each @Label. key to a real @Tag. value.

Concrete example — placing a Pump symbol:

{
  "Type": "Symbol",
  "SymbolName": "Wizard/PUMP",
  "Left": 200, "Top": 300, "Width": 80, "Height": 80,
  "SymbolLabels": [
    { "Type": "SymbolLabel", "Key": "State", "LabelName": "State", "LabelValue": "@Tag.Plant/Pump1/Running.Value", "FieldType": "Expression" },
    { "Type": "SymbolLabel", "Key": "Speed", "LabelName": "Speed", "LabelValue": "@Tag.Plant/Pump1/Speed.Value", "FieldType": "Expression" }
  ]
}

CRITICAL: Use @Tag. bindings in SymbolLabels. NEVER use @Label. when placing symbols in displays — @Label. is only for symbol internal definitions (in DisplaysSymbols).

WizardSymbols — always available, no import needed: TANK, VALVE, PUMP, MOTOR, BLOWER. Place with SymbolName: "Wizard/PUMP".

Library symbols (~1,600 available) auto-import when referenced by SymbolName. No manual import step needed — just reference the path (e.g., "HMI/Equipment/CentrifugalPump").

Sizing: Symbols are vector-based and scale to any proportional size. Use 40x40 for compact, 80x80 for medium, 120x120 for large — the library default is just a starting point. On Canvas, always use minimum 80×80 for process overview displays (see Step 3 minimum sizes).

...

Step 6: Add Visual Dynamics (Canvas Displays Only)

Dynamics attach runtime behaviors to ANY element. They decouple the visual effect from the data binding, so any property of any control can become live.

Discovery: Call list_dynamics() with no parameter to see all available dynamic types, or narrow by category:

list_dynamics()                      -- all dynamics by category
list_dynamics('Color')               -- color-changing dynamics
list_dynamics('Visibility')          -- show/hide dynamics
list_dynamics('Animation')           -- rotation, position, scaling
list_dynamics('Action')              -- click/navigation dynamics
list_dynamics('FillColorDynamic')    -- full schema for a specific dynamic

6a: FillColorDynamic — Color Changes Based on Tag Value

The most common visual dynamic. Changes an element's fill color based on a tag value.

Pattern: A Dynamics array on the element, containing one or more dynamic objects.

{
  "Type": "Rectangle",
  "Left": 100, "Top": 200, "Width": 60, "Height": 60,
  "Dynamics": [
    {
      "Type": "FillColorDynamic",
      "LinkedValue": "@Tag.Plant/Pump1/Running",
      "ChangeColorItems": {
        "Type": "ColorChangeList",
        "Children": [{
  "table_type": "DisplaysList",
  "data": [{
    "ObjectName": "MainPage",
    "PanelType": "Dashboard",
    "DashboardDisplay": {
      "Columns": ["*", "*", "*"],
      "Rows": ["*", "*"]
    },
    "Cells": [
      {
        "Row": 0, "Col": 0,
        "Cell": { "HeaderLink": "Tank 1 Level" },
        "Content": {
          "Type": "CircularGauge",
          "LinkedValue": "@Tag.Plant/Tank1/Level.Value",
          "Minimum": 0, "Maximum": 100
        }
      },
      {
        "Row": 0, "Col": 1,
        "Cell": { "HeaderLink": "Tank 1 Temp" },
        "Content": {
          "Type": "CircularGauge",
          "LinkedValue": "@Tag.Plant/Tank1/Temperature.Value",
          "Minimum": 0, "Maximum": 100
        }
      },
      {
        "Row": 0, "Col": 2,
        "Cell": { "HeaderLink": "Trend" },
        "Content": {
          "Type": "TrendChart",
          "Duration": "5m",
          "Pens": {
          {  "Type": "TrendPenListChangeColorItem",
 "ChangeLimit":           "Children": [
    0, "LimitColor": "#FF808080" },
          { "Type": "TrendPenChangeColorItem", "LinkedValueChangeLimit": "@Tag.Plant/Tank1/Level"1, "PenLabelLimitColor": "Level", "Stroke": "#FF2196F3", "Auto": true }#FF00FF00" }
        ]
      }
    }
  ]
}

For analog values (temperature, pressure), use graduated thresholds:

{
  "Type": "FillColorDynamic",
       }"LinkedValue": "@Tag.Plant/Tank1/Temperature",
        }"ChangeColorItems": {
      }"Type": "ColorChangeList",
      {"Children": [
      {  "RowType": 1"ChangeColorItem", "ColChangeLimit": 0,  "ColSpanLimitColor": "#FF2196F3" 3},
      { "Type": "ChangeColorItem", "CellChangeLimit": {60, "HeaderLinkLimitColor": "Equipment Status#FFFFEB3B" },
       { "ContentType": {
          "Type"ChangeColorItem", "ChangeLimit": 80, "LimitColor": "Symbol#FFFF9800" },
         { "SymbolNameType": "Wizard/TANKChangeColorItem",
 "ChangeLimit":         "SymbolLabels": [95, "LimitColor": "#FFF44336" }
            {]
  }
}

6b: VisibilityDynamic — Show/Hide Elements

{
  "Type": "SymbolLabelTextBlock",
  "KeyText": "ValueALARM",
  "LabelNameLeft": "Value", "LabelValue": "@Tag.Plant/Tank1/Level.Value"100, "Top": 50, "FieldTypeWidth": 80, "ExpressionHeight": }25,
          ]"Dynamics": [
        }{
      }
    ]
  }]
}

Key Dashboard rules:

  • DashboardDisplay defines the grid: Columns and Rows arrays (use "*" for proportional sizing)
  • Each Cell references Row/Col (0-based) and contains a Content element
  • No Left/Top needed — elements auto-fill their cell
  • ColSpan/RowSpan for multi-cell elements
  • Cell.HeaderLink adds a header label to the cell
  • For all controls and symbols verity and change as needed the Height and Widith it will be used.
  • For Symbols modify the height and width proportional to original size, so its resized nicely.

Step 4: 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
list_elements('Wizard')              -- Wizard symbol schema

Symbol Placement with SymbolLabel Binding

Symbols are reusable components with parameterized inputs. When you place a symbol, you use SymbolLabels to connect the symbol's internal @Label. parameters to actual @Tag. values. Here's how the mapping works:

  • Inside a symbol definition (DisplaysSymbols), internal bindings use @Label.State, @Label.Speed, etc.
  • When placing a symbol in a display (DisplaysList), you provide SymbolLabels that map each @Label. key to a real @Tag. value.

Concrete example — placing a Pump symbol:

"Type": "VisibilityDynamic",
      "LinkedValue": "@Tag.Plant/Tank1/AlarmActive"
    }
  ]
}

Element is visible when the linked value is non-zero/true, hidden when zero/false.

6c: RotationDynamic — Rotate Elements

{
  "Type": "Symbol",
  "SymbolName": "Wizard/BLOWER",
  "Left": 200, "Top": 100, "Width": 80, "Height": 80,
  "Dynamics": [
    {
      "Type": "RotationDynamic",
      "LinkedValue": "@Tag.Plant/Fan1/Speed",
      "MinAngle": 0, "MaxAngle": 360,
      "MinValue": 0, "MaxValue": 100
    }
  ]
}

6d: Multiple Dynamics on One Element

Elements can have multiple dynamics simultaneously:

{
  "Type": "Rectangle",
  "Left": 100, "Top": 200, "Width": 60, "Height": 60,
  "Dynamics": [
    {
      "Type": "FillColorDynamic",
      "LinkedValue": "@Tag.Plant/Pump1/Running",
      "ChangeColorItems": {
        "Type": "ColorChangeList",
        "Children": [
          { "Type": "ChangeColorItem", "ChangeLimit": 0, "LimitColor": "#FF808080" },
      {
  "Type": "Symbol",
  "SymbolName": "Wizard/PUMP",
  "Left": 200, "Top": 300, "Width": 80, "Height": 80,
  "SymbolLabels": [
    { "Type": "SymbolLabelChangeColorItem", "KeyChangeLimit": "State"1, "LabelNameLimitColor": "State", "LabelValue": "@Tag.Plant/Pump1/Running.Value", "FieldType": "Expression"#FF4CAF50" }
        ]
      }
    },
    {
      "Type": "SymbolLabelVisibilityDynamic",
 "Key": "Speed", "LabelName": "Speed", "LabelValue     "LinkedValue": "@Tag.Plant/Pump1/Speed.Value", "FieldType": "Expression"Enabled"
    }
  ]
}

The Key matches the symbol's internal @Label. name. The LabelValue provides the actual tag binding using @Tag. prefix. The symbol internally resolves @Label.State → the value of @Tag.Plant/Pump1/Running.Value.

CRITICAL: Use @Tag. bindings in SymbolLabels. NEVER use @Label. when placing symbols in displays — @Label. is only for symbol internal definitions (in DisplaysSymbols).

WizardSymbols — always available, no import needed: TANK, VALVE, PUMP, MOTOR, BLOWER. Place with SymbolName: "Wizard/PUMP". 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 (e.g., "HMI/Equipment/CentrifugalPump").

Sizing: Symbols are vector-based and scale to any proportional size. Use 40x40 for compact, 80x80 for medium, 120x120 for large — the library default is just a starting point.

Step 5: Add Visual Dynamics

Dynamics attach runtime behaviors to ANY element. They decouple the visual effect from the data binding, so any property of any control can become live.

Discovery: Call list_dynamics() with no parameter to see all available dynamic types, or narrow by category:

Always verify dynamic schemas: Call list_dynamics('FillColorDynamic') (or whichever type) for the exact property names and structure.

...

Step 7: Add Click Actions (ActionDynamic)

Typically Navigation is only in Headers displays

For button/navigation behavior:

list_dynamics('ActionDynamic')

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

{
  "Type": "Button",
  "Text": "Go to Details",
  "Left": 50, "Top": 400, "Width": 150, "Height": 40,
  "ActionDynamic": {
    "Type": "NavigateToDisplay",
    "DisplayName": "DetailPage"
  }
}

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

{
  "ActionDynamic": [
    {
      "Event": "MouseLeftButtonDown",
      "Actions": [
list_dynamics()                      -- all dynamics by category
list_dynamics('Color'){ "Type": "NavigateToDisplay", "DisplayName": "DetailPage" }
      ]
    }
     -- color-changing dynamics
list_dynamics('Visibility')          -- show/hide dynamics
list_dynamics('Animation')           -- rotation, position, scaling
list_dynamics('Action')              -- click/navigation dynamics
list_dynamics('FillColorDynamic')    -- full schema for a specific dynamic

5a: FillColorDynamic — Color Changes Based on Tag Value

The most common visual dynamic. Changes an element's fill color based on a tag value.

Pattern: A Dynamics array on the element, containing one or more dynamic objects. Each dynamic has a LinkedValue (data source) and behavior definition.

]
}

...

Step 8: Write the Display

Displays are document objects — full replacement on write.

Creating a new display — complete example:

{
  "table_type": "DisplaysList",
  "data": [{
    "Name": "ProcessOverview",
    "PanelType": "Canvas",
    "DisplayMode": "Page",
    "Size": "1366 x 720",
    "Elements": [
      {
      {
  "Type": "Rectangle",
        "Left": 10020, "Top": 20060, "Width": 60208, "Height": 60320,
        "DynamicsFillTheme": [
  "PanelBackground",
  {
      "TypeStroke": "FillColorDynamic#FF909090", "StrokeThickness": 1
      "LinkedValue": "@Tag.Plant/Pump1/Running"},
      "ChangeColorItems": {
        "Type": "ColorChangeListTextBlock",
        "ChildrenText": ["Process Overview",
        "Left": 20, { "TypeTop": 10, "ChangeColorItemWidth": 400, "ChangeLimitHeight": 0, "LimitColor35,
        "FontSize": "#FF808080" },20
      }
    { "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FF00FF00" }
        ]
      }
    }
  ]
}

This makes the rectangle gray when Running = 0 (stopped) and green when Running = 1 (running). The ChangeLimit values define thresholds — the color applies when the tag value reaches that limit.

]
  }]
}

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

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

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

...

Step 9: CodeBehind (Client-Side Display Logic)

CodeBehind is CLIENT-SIDE C# or VB.NET code embedded in each display for lifecycle events and operator interaction.

Lifecycle methods:

  • DisplayOpening() — runs once when the display loads
  • DisplayIsOpen() — runs cyclically while displayed (interval set by IsOpenInterval)
  • DisplayClosing() — runs once when the display closes
  • DialogOnOK() — runs when a dialog's OK button is clicked

CodeBehind is stored in the Contents field of the display, formatted as {Language}\r\n{Code}.

Minimal example — a button click that writes a tag valueFor analog values (temperature, pressure), use graduated thresholds:

{
  "Typetable_type": "FillColorDynamicDisplaysList",
  "LinkedValuedata": "@Tag.Plant/Tank1/Temperature",
  "ChangeColorItems": [{
    "TypeName": "ColorChangeListControlPage",
    "ChildrenPanelType": [
      { "Type": "ChangeColorItem", "ChangeLimit": 0,  "LimitColor": "#FF2196F3" },
   Canvas",
   { "TypeSize": "ChangeColorItem1366 x 720",
  "ChangeLimit": 60, "LimitColorContents": "#FFFFEB3B" },
      { "Type": "ChangeColorItem", "ChangeLimit": 80, "LimitColor": "#FFFF9800" },
      { "Type": "ChangeColorItem", "ChangeLimit": 95, "LimitColor": "#FFF44336" }
    ]
  }
}

5b: VisibilityDynamic — Show/Hide Elements

{
  CSharp\r\nvoid DisplayOpening()\r\n{\r\n    // Initialize display state\r\n}\r\n\r\nvoid ButtonStart_Click(object sender, EventArgs e)\r\n{\r\n    @Tag.Plant/Pump1/Command.Value = 1;\r\n}\r\n\r\nvoid ButtonStop_Click(object sender, EventArgs e)\r\n{\r\n    @Tag.Plant/Pump1/Command.Value = 0;\r\n}",
    "Elements": [
      {
        "Type": "TextBlockButton",
        "Text": "ALARMStart Pump",
        "Left": 10050, "Top": 50100, "Width": 80120, "Height": 2540,
   "Dynamics": [
      {"ClickEvent": "ButtonStart_Click"
      "Type": "VisibilityDynamic"},
      "LinkedValue": "@Tag.Plant/Tank1/AlarmActive"{
    }
  ]
}

Element is visible when the linked value is non-zero/true, hidden when zero/false.

5c: RotationDynamic — Rotate Elements

{
  "Type": "SymbolButton",
  "SymbolName      "Text": "Wizard/BLOWERStop Pump",
        "Left": 200, "Top": 100, "Width": 80120, "Height": 80,
  "Dynamics": [
    {
      "Type": "RotationDynamic",
40,
        "LinkedValueClickEvent": "@Tag.Plant/Fan1/Speed",
ButtonStop_Click"
      },
   "MinAngle": 0, "MaxAngle": 360, {
        "MinValueType": 0, "MaxValueTextBlock": 100,
    }
  ]
}

5d: Multiple Dynamics on One Element

Elements can have multiple dynamics simultaneously:

{
  "TypeLinkedValue": "Rectangle",
{@Tag.Plant/Pump1/Running.Value}",
        "Left": 10050, "Top": 200160, "Width": 60200, "Height": 60,25
  "Dynamics": [
    {}
      "Type": "FillColorDynamic",
      "LinkedValue": "@Tag.Plant/Pump1/Running",
      "ChangeColorItems": {
        "Type": "ColorChangeList",
        "Children": [
          { "Type": "ChangeColorItem", "ChangeLimit": 0, "LimitColor": "#FF808080" },
          { "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FF4CAF50" }
        ]
      }
    },
    {
      "Type": "VisibilityDynamic",
      "LinkedValue": "@Tag.Plant/Pump1/Enabled"
    }
  ]
}

Always verify dynamic schemas: Call list_dynamics('FillColorDynamic') (or whichever type) for the exact property names and structure. The examples above are patterns — the schema is the source of truth.

Step 6: Add Click Actions (ActionDynamic)

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",
  "Left": 50, "Top": 400, "Width": 150, "Height": 40,
  "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 7: Write the Display

Displays are document objects — full replacement on write.

Creating a new display: Write the full object as shown in Steps 2–3.

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

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

Then modify the content and write the full object.

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

Step 8: CodeBehind (Client-Side Display Logic)

CodeBehind is CLIENT-SIDE C# or VB.NET code embedded in each display for lifecycle events and operator interaction.

Lifecycle methods:

  • DisplayOpening() — runs once when the display loads
  • DisplayIsOpen() — runs cyclically while displayed (interval set by IsOpenInterval)
  • DisplayClosing() — runs once when the display closes
  • DialogOnOK() — runs when a dialog's OK button is clicked

CodeBehind is stored in the Contents field of the display, formatted as {Language}\r\n{Code}.

Minimal example — a button click that writes a tag value:

{
  "table_type": "DisplaysList",
  "data": [{
    "ObjectName": "ControlPage",
    "PanelType": "Canvas",
    "Size": "1366 x 728",
    "Contents": "CSharp\r\nvoid DisplayOpening()\r\n{\r\n    // Initialize display state\r\n}\r\n\r\nvoid ButtonStart_Click(object sender, EventArgs e)\r\n{\r\n    @Tag.Plant/Pump1/Command.Value = 1;\r\n}\r\n\r\nvoid ButtonStop_Click(object sender, EventArgs e)\r\n{\r\n    @Tag.Plant/Pump1/Command.Value = 0;\r\n}",
    "Elements": [
      {
        "Type": "Button",
        "Text": "Start Pump",
        "Left": 50, "Top": 100, "Width": 120, "Height": 40,
        "ClickEvent": "ButtonStart_Click"
      },
      {
        "Type": "Button",
        "Text": "Stop Pump",
        "Left": 200, "Top": 100, "Width": 120, "Height": 40,
        "ClickEvent": "ButtonStop_Click"
      },
      {
        "Type": "TextBlock",
        "LinkedValue": "{@Tag.Plant/Pump1/Running.Value}",
        "Left": 50, "Top": 160, "Width": 200, "Height": 25
      }
    ]
  }]
}

Key CodeBehind rules:

  • Contents field format: first line is language (CSharp or VBdotNet), followed by \r\n, then code
  • Use @ prefix for runtime object access in code: @Tag.Path/Name.Value, @Client.Context.AssetPath
  • CodeBehind runs in the client, not the server — it's for UI logic, not server-side computation
  • For server-side logic, use the Scripts module (ScriptsTasks, ScriptsClasses)

See the Scripts and Expressions skill for full CodeBehind and server-side scripting guidance.

Step 9: Configure Layouts

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

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

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 10: 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 11: Theme and Color Management

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

Best practice: OMIT color properties to use themed defaults. Only specify colors when intentionally overriding for process-specific meaning (alarm red, water blue, etc.).

Pattern — set color AND clear theme (when you must override):

{
  "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.

Use list_elements('ThemeColors') for available named theme brushes (StateOK, StateAlarm, Water, AlarmHighPriority, etc.).

Common Pitfalls

]
  }]
}

Key CodeBehind rules:

  • Contents field format: first line is language (CSharp or VBdotNet), followed by \r\n, then code
  • Use @ prefix for runtime object access in code: @Tag.Path/Name.Value, @Client.Context.AssetPath
  • CodeBehind runs in the client, not the server — it's for UI logic, not server-side computation

See the Scripts and Expressions skill for full CodeBehind and server-side scripting guidance.

...

Step 10: Configure Layouts

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

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

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 10b: Header Navigation Buttons (Multi-Page Solutions)

When a solution has multiple content pages (Dashboard, ProcessOverview, AlarmPage, TrendPage, etc.), navigation buttons belong in the Header display — not on each content page.

Why the Header

The Header region is persistent — it stays visible while the Content region changes. Placing navigation here means every page gets consistent navigation without duplicating buttons on each content page.

Workflow

  1. Read the Startup layout to find the Header display name:

    get_objects('DisplaysLayouts', names=['Startup'], detail='full')
    
  2. Read the Header display (it's a Canvas page assigned to the Header region):

    get_objects('DisplaysList', names=['Header'], detail='full')
    
  3. Add navigation buttons to the Header display — right-aligned in the header bar:

    {
      "Type": "Button",
      "Text": "Dashboard",
      "Left": 900, "Top": 5, "Width": 110, "Height": 32,
      "ActionDynamic": {
        "Type": "NavigateToDisplay",
        "DisplayName": "MainPage"
      }
    },
    {
      "Type": "Button",
      "Text": "Process",
      "Left": 1020, "Top": 5, "Width": 110, "Height": 32,
      "ActionDynamic": {
        "Type": "NavigateToDisplay",
        "DisplayName": "ProcessOverview"
      }
    },
    {
      "Type": "Button",
      "Text": "Alarms",
      "Left": 1140, "Top": 5, "Width": 110, "Height": 32,
      "ActionDynamic": {
        "Type": "NavigateToDisplay",
        "DisplayName": "AlarmPage"
      }
    }
    
  4. Write back the modified Header display (read-modify-write — it's a document object).

Button Placement in the Header

  • Header displays are typically 1366×40 to 1366×50
  • Navigation buttons go right-aligned (leave left side for title/logo)
  • Standard button size in headers: 100–120px wide × 30–35px tall
  • Gap between buttons: 10px

CRITICAL Rule

Do NOT put page navigation buttons on content pages (Canvas or Dashboard). Content pages should only contain buttons for in-page actions (start/stop, acknowledge, open popup). Page-to-page navigation is always in the Header.

Exception: If the solution has NO layout (single standalone page), navigation buttons can go on the content page itself — but this is unusual for production solutions.

...

Step 11: 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 12: Theme and Color Management

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

Key theme colors for displays:

Theme NamePurposeUsage
PageBackgroundDisplay page backgroundSet on display properties
PanelBackgroundZone/section backgroundsDefault for zone Rectangles
PopupBackgroundHigher contrast panelsUse for callouts, modal-like zones
ControlBackgroundControl backgroundsAuto-applied to gauges, charts
TextForegroundText colorAuto-applied to TextBlocks
DefaultBorderBorder colorAuto-applied to control borders

Best practice: OMIT color properties to use themed defaults. Only specify colors when intentionally overriding for process-specific meaning (alarm red, water blue, etc.).

Pattern — set color AND clear theme (when you must override):

{
  "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.

Use list_elements('ThemeColors') for available named theme brushes (StateOK, StateAlarm, Water, AlarmHighPriority, etc.).

...

Canvas Layout Checklist (Verify Before Writing)

Before writing any Canvas display, verify:

  • [ ] Zones calculated to fill the FULL canvas width and height
  • [ ] Every zone has a background Rectangle with FillTheme: "PanelBackground"
  • [ ] Zone backgrounds are FIRST in the Elements array
  • [ ] Symbols are ≥ 80×80 pixels, centered in their zones
  • [ ] Zone titles are FontSize ≥ 14
  • [ ] Value text is FontSize ≥ 12
  • [ ] Value and Unit are in the SAME TextBlock (e.g., "Flow: {@Tag.X} GPM")
  • [ ] Flow arrows (→) placed between adjacent zones
  • [ ] Bottom panel fills remaining vertical space
  • [ ] Bottom panel sections have PanelBackground theme Rectangles
  • [ ] No element extends beyond the size of display
  • [ ] Page navigation is in the Header display, NOT on content pages
  • [ ] Display identifier field is Name (not ObjectName)
  • [ ] PanelType is set at top level (never inside JsonFormat)

...

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 at top level
Using ObjectName instead of NameHallucinated field nameThe identifier field is Name
Using JsonFormat wrapperOutdated bug workaroundNEVER use JsonFormat. All properties go at top level
Using @Label. in display elementsConfused with symbol definitions@Label. is only for DisplaysSymbols internals. Use @Tag. when placing symbols
Not discovering dynamics firstDoesn't know what's availableCall list_dynamics() with no parameter to see all dynamic types
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: ''} — or just omit to use themed defaults
Sending partial display contentForget it's a document objectAlways read-modify-write for existing displays
Canvas positioning in DashboardMixing layout paradigmsCanvas uses Left/Top + Elements; Dashboard uses Row/Col + Cells
Referencing symbols with wrong pathIncomplete library pathBrowse with list_elements('Symbol/HMI') to get exact paths
Guessing dynamic property namesDifferent dynamics have different schemasAlways call list_dynamics('DynamicTypeName') for exact schema
CodeBehind in wrong Contents formatMissing language prefixContents must start with CSharp\r\n or VBdotNet\r\n before code
All Canvas content in top-leftNo zone planningCalculate zones FIRST to fill the full canvas
Symbols at 40×40 or 50×50Copying library defaultsMinimum 80×80 on process overview displays
Value and Unit as separate TextBlocksSeems logical but misalignsSingle TextBlock: "{@Tag.X} GPM"
No background rectangles on zonesDoesn't know about visual groupingAlways add Rectangle with FillTheme: "PanelBackground"
Hardcoded fill colors on zone backgroundsBreaks in Dark themeUse FillTheme: "PanelBackground" instead
FontSize 10–11 for valuesDefault seemed fineMinimum 13, prefer 14 for operator readability
Navigation buttons on content pagesDoesn't know about Header patternPage navigation goes in the Header display (Step 10b). Content pages only get in-page action buttons
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. Use @Tag. when placing symbols
Not discovering dynamics firstDoesn't know what's availableCall list_dynamics() with no parameter to see all dynamic types
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: ''} — or just omit to use themed defaults
Sending partial display contentForget it's a document objectAlways read-modify-write for existing displays
Canvas positioning in DashboardMixing layout paradigmsCanvas uses Left/Top + Elements; Dashboard uses Row/Col + Cells
Referencing symbols with wrong pathIncomplete library pathBrowse with list_elements('Symbol/HMI') to get exact paths
Guessing dynamic property namesDifferent dynamics have different schemasAlways call list_dynamics('DynamicTypeName') for exact schema
CodeBehind in wrong Contents formatMissing language prefixContents must start with CSharp\r\n or VBdotNet\r\n before code

...

Quick Reference

Display ActionTool Call
Get display schemaget_table_schema('DisplaysList')
Get Canvas structurelist_elements('Canvas')
Get Dashboard structurelist_elements('Dashboard')
Get element schemalist_elements('ElementName')
Browse all dynamicslist_dynamics()
Get specific dynamic schemalist_dynamics('FillColorDynamic')
Browse symbolslist_elements('Symbol/HMI') or list_elements('Library')
Browse Wizard symbolslist_elements('Wizard')
Browse theme colorslist_elements('ThemeColors')
Read existing displayget_objects('DisplaysList', names=['PageName'], detail='full')
Write displaywrite_objects('DisplaysList', data=[...])
Read layout (for Header name)get_objects('DisplaysLayouts', names=['Startup'], detail='full')
Read Header for nav buttonsget_objects('DisplaysList', names=['Header'], detail='full')
Navigate Designer to displaydesigner_action('navigate', 'Display.PageName')
Browse runtime propertiesbrowse_runtime_properties('Client')

...