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, symbol placement

...

with SymbolLabel binding, visual dynamics (color, visibility, rotation), ActionDynamic click handling, CodeBehind for display logic, and asset-driven navigation" version: "

...

2.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 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 visual dynamics (color changes, visibility toggles, rotation)
  • Need navigation between displays
  • Need asset-driven dynamic displays

...

  • Need CodeBehind for client-side logic

Do NOT use when:

  • Just modifying text or properties on an existing display (use get_objects + write_objects)
  • Need only

...

  • a basic starter dashboard (the New Solution skill covers that)

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, browse_runtime_properties
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:

Discovery Entry Points

Before building any display, use these tools to discover what's available:

list_elements()                  -- all element types by category + library folders
list_dynamics()                  -- ALL dynamic types by category (Color, Visibility, Action, Animation, etc.)
list_elements('Symbol/HMI')     -- browse symbol library
list_elements('Canvas')          -- Canvas display structurelist_elements('TextBlock')           -- specific control schema
list_elements('CircularGaugeDashboard')       -- specificDashboard display controlstructure schema

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

list_elements('TextBlock,CircularGauge,Rectangle')
list_elements('Shapes')              -- all drawing primitives
list_elements('Interaction')         -- 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')
{
  "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": "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

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": "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" }
  ]
}

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:

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

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.

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

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

{
  "Type": "FillColorDynamic",
  "LinkedValue": "@Tag.Plant/Tank1/Temperature",
  "ChangeColorItems": {
    "Type": "ColorChangeList",
    "Children": [
      { "Type": "ChangeColorItem", "ChangeLimit": 0,  "LimitColor": "#FF2196F3" },
      { "Type": "ChangeColorItem", "ChangeLimit": 60, "LimitColor": "#FFFFEB3B" },
      { "Type": "ChangeColorItem", "ChangeLimit": 80, "LimitColor": "#FFFF9800" },
      { "Type": "ChangeColorItem", "ChangeLimit": 95, "LimitColor": "#FFF44336" }
    ]
  }
}

5b: VisibilityDynamic — Show/Hide Elements

{
  "Type": "TextBlock",
  "Text": "ALARM",
  "Left": 100, "Top": 50, "Width": 80, "Height": 25,
  "Dynamics": [
    {
      "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": "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
    }
  ]
}

5d: 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": {
'Shapes')          "Type": "ColorChangeList",
    -- all drawing primitives (Canvas only)
list_elements('Interaction') "Children": [
          -- buttons, inputs, toggles
list_elements('Charts')  { "Type": "ChangeColorItem", "ChangeLimit": 0, "LimitColor": "#FF808080" },
          {  -- TrendChart, BarChart, PieChart
list_elements('Gauges')  "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FF4CAF50" }
         ]
   -- 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": "TextBlockVisibilityDynamic",
      "TextLinkedValue": "Tank Level",
  "Left": 100, "Top": 50, "Width": 200, "Height": 30
}

...

"@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": "TextBlockButton",
  "Text": "TankGo to LevelDetails",
  "ColumnLeft": 050, "RowTop": 0400, "ColumnSpanWidth": 2150, "RowSpanHeight": 1
}

Data Binding

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

{
40,
  "ActionDynamic": {
    "Type": "TextBlockNavigateToDisplay",
    "TextDisplayName": "@Tag.Plant/Tank1/Level.Value",DetailPage"
  "Foreground": "#FFFFFF"}
}

Step 3: Place Symbols

Browse available symbols:

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

{
  "ActionDynamic": [
    {
      "Event": "MouseLeftButtonDown",
list_elements('Symbol')              -- all symbol categories
list_elements('Symbol/HMI')         -- HMI symbol library
list_elements('Symbol/HMI/Equipment') -- equipment symbols
list_elements('Library')        "Actions": [
     -- all library folders

Symbol placement pattern:

{
  "Type": "SymbolNavigateToDisplay",
  "SymbolNameDisplayName": "HMI/Equipment/Pump",DetailPage" }
      ]
  "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:

 }
  ]
}

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": [{
  "Type": "Button",
  "Text": "Go to Details",
  "ActionDynamic": {
    "TypeObjectName": "NavigateToDisplayControlPage",
    "DisplayNamePanelType": "DetailPageCanvas",
  }
}

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

{
  "ActionDynamicSize": [
"1366    {
      "Event": "MouseLeftButtonDownx 728",
      "ActionsContents": [
 "CSharp\r\nvoid DisplayOpening()\r\n{\r\n    // Initialize  { "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": [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": "TextBlockButton",
        "Text": "PlantStop OverviewPump",
        "Column"Left": 200, "Top": 0100, "RowWidth": 0120, "ColumnSpanHeight": 4,40,
        "FontSizeClickEvent": 24"ButtonStop_Click"
      },
      {
        "Type": "CircularGaugeTextBlock",
        "ValueLinkedValue": "{@Tag.Plant/Tank1Pump1/LevelRunning.Value}",
        "Left": 50,   "Top": 160, "ColumnWidth": 0200, "RowHeight": 1
 25
      }
       }
    ]
  }]
}

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.

...

]
  }]
}

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

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')
Read existing displayget_objects('DisplaysList', names=['PageName'], detail='full')
Write displaywrite_objects('DisplaysList', data=[...])
Navigate Designer to displaydesigner_action('navigate', 'Display.PageName')
Browse runtime propertiesbrowse_runtime_properties('Client')

What's Next

  • Server-side logic, tag calculations, CodeBehind patterns: Load the Scripts and Expressions skill — search_docs('scripts', labels='skill')
  • New solution from scratch: Load the New Solution skill — search_docs('new solution', labels='skill')
  • Specific protocol connectivity: Use list_protocols() to browse, then search_docs('modbus', labels='connector') for protocol-specific docs
  • Advanced symbol creation: search_docs('custom symbols', labels='tutorial') for building reusable parameterized symbols

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

...