---
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: "34.0"
author: "Tatsoft"
---
...
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, place page navigation buttons in the persistent Header display, write CodeBehind for display lifecycle events, and set up asset-driven navigation patterns.New in v3: Zone-based Canvas layout methodology that produces professional, well-spaced process overview screens without manual adjustment.
Use when:
...
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.
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:
Dashboard — responsive grid (Columns/Rows/Cells). Use for:
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.
AI agents consistently produce poorly laid out Canvas displays when placing elements with arbitrary coordinates. Always use the zone-based approach described here.
NEVER start by placing individual elements. Instead:
Before writing ANY element, mentally divide the 1366×728 canvas into rectangular zones. Each zone represents a process area, equipment group, or information panel.
Standard zone layout for a process overview (1366×728):
????????????????????????????????????????????????????????????????
? TITLE BAR (x:0, y:0, w:1366, h:50) NAV BTN ?
????????????????????????????????????????????????????????????????
? Zone 1 ? Zone 2 ? Zone 3 ? Zone 4 ? Zone 5 ? Zone 6 ? Zone 7 ?
? ? ? ? ? ? ? ?
? h:300 ? ? ? ? ? ? ?
????????????????????????????????????????????????????????????????
? BOTTOM PANEL: Trend, Alarms, KPIs, or ML Status (h:300+) ?
????????????????????????????????????????????????????????????????
Zone sizing rules:
For N process stages on a 1366×728 canvas:
Available width = 1366 - 40 (left/right margin 20px each) = 1326
Available height = 728 - 50 (title) - 20 (top margin) = 658
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
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
}
Higher contrast zones (rare — popups, callouts): use PopupBackground theme. Not needed for normal process zones.
Page background should use PageBackground theme (set on the display properties, not as a rectangle).
CRITICAL theming rules for zone backgrounds:
FillTheme: "PanelBackground" — this respects Light/Dark theme switching automatically"Fill": "#0A000000" — these break in Dark themeFill + 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.
Every process zone follows the same internal layout pattern. Coordinates are offsets from the zone's Left/Top origin:
???? Zone (background Rectangle) ????????????
? ZONE TITLE (FontSize 15-16) y+10 ?
? ?
? ???????????? y+40 ?
? ? Symbol ? 80×80 to 100×100 ?
? ? (pump, ? centered horizontally ?
? ? motor) ? ?
? ???????????? y+140 ?
? ?
? 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
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)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.
The bottom panel (below process zones) should contain ONE of:
Option A: Trend + KPI sidebar
??????????????? Bottom Panel ???????????????????
? TrendChart (w:900, 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 ???????????????????
? Status: Score: 0.023 Confidence: 0.98 ? (h:30)
? TrendChart (full width, h:250) ?
????????????????????????????????????????????????
Bottom panel sections should also use PanelBackground theme background rectangles.
| Element | Minimum Size | Recommended |
|---|---|---|
| Wizard Symbol | 70×70 | 80×80 to 100×100 |
| Library Symbol | 60×60 | 80×80 |
| Section Title | FontSize 14 | FontSize 15–16 |
| Value Text | FontSize 13 | FontSize 14 |
| Unit Label | FontSize 11 | FontSize 12 |
| Gauge (Circular) | 150×150 | 180×180 |
| TrendChart | 300×180 | 500×200 |
| AlarmViewer | 400×150 | 600×200 |
| Button | 100×35 | 130×40 |
Dashboard uses a grid with DashboardDisplay (columns/rows definition) and Cells array.
list_elements('Dashboard')
...
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
}
]
}
...
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:
Dashboard — responsive grid (Columns/Rows/Cells). Use for:
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.
...
AI agents consistently produce poorly laid out Canvas displays when placing elements with arbitrary coordinates. Always use the zone-based approach described here.
NEVER start by placing individual elements. Instead:
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 2 | Zone 2 | Zone 2 | Zone 2 | Zone 2 | Zone 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:
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
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:
FillTheme: "PanelBackground" — this respects Light/Dark theme switching automatically"Fill": "#0A000000" — these break in Dark themeFill + 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.
Every process zone follows the same internal layout pattern. Coordinates are offsets from the zone's Left/Top origin:
| Panel | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
|
Symbol centering formula:
Symbol.Left = Zone.Left + (Zone.Width - Symbol.Width) / 2
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)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.
The bottom panel (below process zones) should contain ONE of:
Option A: Trend + KPI sidebar
Bottom Panel | |
|---|---|
TrendChart (w:700, h:280) |
|
Option B: Trend + Alarm split
Bottom Panel | |
|---|---|
TrendChart (w:660, h:280) |
|
Option C: Full-width trend with status bar
Bottom Panel (h:30) | |
|---|---|
Status: Score: 0.023 Confidence: 0.98
|
Bottom panel sections should also use PanelBackground theme background rectangles.
...
| Element | Minimum Size | Recommended |
|---|---|---|
| Wizard Symbol | 70×70 | 80×80 to 100×100 |
| Library Symbol | 60×60 | 80×80 |
| Section Title | FontSize 14 | FontSize 15–16 |
| Value Text | FontSize 13 | FontSize 14 |
| Unit Label | FontSize 11 | FontSize 12 |
| Gauge (Circular) | 150×150 | 180×180 |
| TrendChart | 300×180 | 500×200 |
| AlarmViewer | 400×150 | 600×200 |
| Button | 100×35 | 130×40 |
...
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)Cell references Row/Col (0-based) and contains a Content elementColSpan/RowSpan for multi-cell elementsCell.HeaderLink adds a header label to the cell...
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
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.
@Label.State, @Label.Speed, etc.@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).
...
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(){ "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",-- all dynamics by category list_dynamics('Color')"Children":[-- color-changing dynamics list_dynamics('Visibility'){"Type": "TrendPen", "LinkedValue": "@Tag.Plant/Tank1/Level", "PenLabel": "Level", "Stroke": "#FF2196F3", "Auto": true }-- show/hide dynamics list_dynamics('Animation') -- rotation, position, scaling list_dynamics('Action')]-- click/navigation dynamics list_dynamics('FillColorDynamic') -- full schema for a specific dynamic
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",
}
}
},
{
"Row": 1, "Col": 0, "ColSpan": 3,
"Cell": { "HeaderLink": "Equipment Status" },
"ContentLinkedValue": {
"@Tag.Plant/Pump1/Running",
"TypeChangeColorItems": "Symbol",
{
"SymbolNameType": "Wizard/TANKColorChangeList",
"SymbolLabelsChildren": [
{ "Type": "SymbolLabelChangeColorItem", "KeyChangeLimit": "Value"0, "LabelNameLimitColor": "Value#FF808080", "LabelValue": "@Tag.Plant/Tank1/Level.Value", "FieldType": "Expression" } },
]
}
}
]
}]
}
Key Dashboard rules:
DashboardDisplay defines the grid: Columns and Rows arrays (use "*" for proportional sizing)Cell references Row/Col (0-based) and contains a Content elementColSpan/RowSpan for multi-cell elementsCell.HeaderLink adds a header label to the cellBrowse available symbols:
list_elements('Symbol'){ "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FF00FF00" } ] }--all}symbolcategories 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
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.
@Label.State, @Label.Speed, etc.@Label. key to a real @Tag. value....
]
}
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" }
]
}
}
{
"Type": "SymbolTextBlock",
"SymbolNameText": "Wizard/PUMPALARM",
"Left": 200100, "Top": 30050, "Width": 80, "Height": 8025,
"SymbolLabelsDynamics": [
{ "Type": "SymbolLabel", "Key": "State", "LabelName": "State", "LabelValue": "@Tag.Plant/Pump1/Running.Value", "FieldType": "Expression" },
{ "Type": "SymbolLabel", "Key": "Speed", "LabelNameType": "SpeedVisibilityDynamic",
"LabelValueLinkedValue": "@Tag.Plant/Pump1Tank1/Speed.Value", "FieldType": "Expression"AlarmActive"
}
]
}
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).
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:
Element is visible when the linked value is non-zero/true, hidden when zero/false.
{
"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
}
]
}
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": [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'){ "Type": "ChangeColorItem", "ChangeLimit":-- click/navigation dynamics list_dynamics('FillColorDynamic')0, "LimitColor": "#FF808080" },--fullschemaforaspecificdynamic
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": "RectangleChangeColorItem",
"LeftChangeLimit": 1001, "TopLimitColor": 200, "Width": 60, "Height": 60,
"Dynamics": [#FF4CAF50" }
]
}
},
{
"Type": "FillColorDynamicVisibilityDynamic",
"LinkedValue": "@Tag.Plant/Pump1/Running",
"ChangeColorItems": {
"@Tag.Plant/Pump1/Enabled"
}
]
}
Always verify dynamic schemas: Call list_dynamics('FillColorDynamic') (or whichever type) for the exact property names and structure.
...
Typically Navigation is only in Headers displays
For button/navigation behavior:
list_dynamics('ActionDynamic')
Simple format — single action, auto-mapped to MouseLeftButtonDown:
{ "Type": "ColorChangeListButton", "Children": [ { "TypeText": "ChangeColorItem", "ChangeLimit": 0, "LimitColor": "#FF808080" }, { "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FF00FF00" } ] } } ] }Go to Details", "Left": 50, "Top": 400, "Width": 150, "Height": 40, "ActionDynamic": { "Type": "NavigateToDisplay", "DisplayName": "DetailPage" } }
Nested format — multi-event, explicit mouse event mappingFor analog values (temperature, pressure), use graduated thresholds:
{
"TypeActionDynamic": "FillColorDynamic",[
"LinkedValue": "@Tag.Plant/Tank1/Temperature",
"ChangeColorItems": {
"TypeEvent": "ColorChangeListMouseLeftButtonDown",
"ChildrenActions": [
{ "Type": "ChangeColorItemNavigateToDisplay", "ChangeLimitDisplayName": 0, "LimitColor": "#FF2196F3" }, "DetailPage" }
]
}
]
}
...
Displays are document objects — full replacement on write.
Creating a new display — complete example:
{
"Typetable_type": "ChangeColorItemDisplaysList",
"ChangeLimitdata": 60, [{
"LimitColorName": "#FFFFEB3BProcessOverview" },
{ "TypePanelType": "ChangeColorItemCanvas",
"ChangeLimit": 80, "LimitColorDisplayMode": "#FFFF9800Page" },
{ "TypeSize": "ChangeColorItem1366 x 720",
"ChangeLimit": 95, "LimitColorElements": "#FFF44336" }[
]
}
}
{
"Type": "TextBlockRectangle",
"Text": "ALARM",
"Left": 10020, "Top": 5060, "Width": 80208, "Height": 25,
"Dynamics": [320,
{
"TypeFillTheme": "VisibilityDynamicPanelBackground",
"LinkedValueStroke": "#FF909090", "@Tag.Plant/Tank1/AlarmActive"StrokeThickness": 1
},
]
}
Element is visible when the linked value is non-zero/true, hidden when zero/false.
{
{
"Type": "SymbolTextBlock",
"SymbolName "Text": "Wizard/BLOWERProcess Overview",
"Left": 20020, "Top": 10010, "Width": 80400, "Height": 8035,
"Dynamics "FontSize": [20
{}
"Type": "RotationDynamic",
"LinkedValue": "@Tag.Plant/Fan1/Speed",
"MinAngle": 0, "MaxAngle": 360,
"MinValue": 0, "MaxValue": 100
}
]
}
]
}]
}
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.
...
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 loadsDisplayIsOpen() — runs cyclically while displayed (interval set by IsOpenInterval)DisplayClosing() — runs once when the display closesDialogOnOK() — runs when a dialog's OK button is clickedCodeBehind is stored in the Contents field of the display, formatted as {Language}\r\n{Code}.
Minimal example — a button click that writes a tag valueElements can have multiple dynamics simultaneously:
{
"Typetable_type": "RectangleDisplaysList",
"Leftdata": 100,[{
"TopName": 200"ControlPage",
"WidthPanelType": 60"Canvas",
"HeightSize": 60 "1366 x 720",
"DynamicsContents": [
"CSharp\r\nvoid DisplayOpening()\r\n{\r\n {
// Initialize display "Type": "FillColorDynamic",
"LinkedValue": "state\r\n}\r\n\r\nvoid ButtonStart_Click(object sender, EventArgs e)\r\n{\r\n @Tag.Plant/Pump1/Running",
"ChangeColorItems": {
"Type": "ColorChangeList",
"ChildrenCommand.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": "ChangeColorItemButton", "ChangeLimit": 0, "LimitColor
"Text": "#FF808080Start Pump" },
{ "TypeLeft": "ChangeColorItem"50, "ChangeLimitTop": 1100, "LimitColorWidth": "#FF4CAF50" }
]
}
},
{
"Type": "VisibilityDynamic"120, "Height": 40,
"LinkedValueClickEvent": "@Tag.Plant/Pump1/EnabledButtonStart_Click"
},
]
}
Always verify dynamic schemas: Call list_dynamics('FillColorDynamic') (or whichever type) for the exact property names and structure.
For button/navigation behavior:
list_dynamics('ActionDynamic')
Simple format — single action, auto-mapped to MouseLeftButtonDown:
{
{
"Type": "Button",
"Text": "GoStop to DetailsPump",
"Left": 50200, "Top": 400100, "Width": 150120, "Height": 40,
"ActionDynamic": {
"TypeClickEvent": "NavigateToDisplayButtonStop_Click",
"DisplayName": "DetailPage"
}
}
...
,...
{
"ActionDynamic": [
{
"EventType": "MouseLeftButtonDownTextBlock",
"ActionsLinkedValue": [
"{@Tag.Plant/Pump1/Running.Value}",
{ "TypeLeft": 50, "NavigateToDisplayTop": 160, "DisplayNameWidth": 200, "DetailPageHeight": }25
]}
}]
]
}
Displays are document objects — full replacement on write.
Creating a new display: Write the full object as shown in Steps 2–4.
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.
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 loadsDisplayIsOpen() — runs cyclically while displayed (interval set by IsOpenInterval)DisplayClosing() — runs once when the display closesDialogOnOK() — runs when a dialog's OK button is clickedCodeBehind 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:
...
}]
}
Key CodeBehind rules:
Contents field format: first line is language (CSharp or VBdotNet), followed by \r\n, then code@ prefix for runtime object access in code: @Tag.Path/Name.Value, @Client.Context.AssetPathSee the Scripts and Expressions skill for full CodeBehind and server-side scripting guidance.
...
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:
Layouts are document objects — read first, modify, write back. Dependencies: DisplaysSymbols → DisplaysList → DisplaysLayouts
...
When a solution has multiple content pages (Dashboard, ProcessOverview, AlarmPage, TrendPage, etc.), navigation buttons belong in the Header display — not on each content page.
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.
Read the Startup layout to find the Header display name:
get_objects('DisplaysLayouts', names=['Startup'], detail='full')
Read the Header display (it's a Canvas page assigned to the Header region):
get_objects('DisplaysList', names=['Header'], detail='full')
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"
}...
}
Key CodeBehind rules:
Contents field format: first line is language (CSharp or VBdotNet), followed by \r\n, then code@ prefix for runtime object access in code: @Tag.Path/Name.Value, @Client.Context.AssetPathSee the Scripts and Expressions skill for full CodeBehind and server-side scripting guidance.
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:
Write back the modified Header display (read-modify-write — it's a document object).
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.Layouts are document objects — read first, modify, write back. Dependencies: DisplaysSymbols → DisplaysList → DisplaysLayouts
...
...
FillTheme: "PanelBackground""Flow: {@Tag.X} GPM")PanelBackground theme RectanglesName (not ObjectName)PanelType is set at top level (never inside JsonFormat)...
| Mistake | Why It Happens | How to Avoid |
|---|---|---|
Using DisplaysDraw as table_type | Confused with DisplaysList | DisplaysDraw is the visual editor UI, not a writable table |
| Omitting PanelType | Defaults silently to Canvas | Always set PanelType explicitlyPanelType explicitly at top level |
Using ObjectName instead of Name | Hallucinated field name | The identifier field is Name |
Using JsonFormat wrapper | Outdated bug workaround | NEVER use JsonFormat. All properties go at top level |
Using @Label. in display elements | Confused with symbol definitions | @Label. is only for DisplaysSymbols internals. Use @Tag. when placing symbols |
| Not discovering dynamics first | Doesn't know what's available | Call list_dynamics() with no parameter to see all dynamic types |
| Screenshots for self-validation | Habit from other tools | Trust write_objects success. User sees live updates |
| Setting colors without clearing theme | Theme overrides custom colors | Set value AND clear theme: {Fill: '#FF3498DB', FillTheme: ''} — or just omit to use themed defaults |
| Sending partial display content | Forget it's a document object | Always read-modify-write for existing displays |
| Canvas positioning in Dashboard | Mixing layout paradigms | Canvas uses Left/Top + Elements; Dashboard uses Row/Col + Cells |
| Referencing symbols with wrong path | Incomplete library path | Browse with list_elements('Symbol/HMI') to get exact paths |
| Guessing dynamic property names | Different dynamics have different schemas | Always call list_dynamics('DynamicTypeName') for exact schema |
| CodeBehind in wrong Contents format | Missing language prefix | Contents must start with CSharp\r\n or VBdotNet\r\n before code |
| All Canvas content in top-left | No zone planning | Calculate zones FIRST to fill the full canvas |
| Symbols at 40×40 or 50×50 | Copying library defaults | Minimum 80×80 on process overview displays |
| Value and Unit as separate TextBlocks | Seems logical but misaligns | Single TextBlock: "{@Tag.X} GPM" |
| No background rectangles on zones | Doesn't know about visual grouping | Always add Rectangle with FillTheme: "PanelBackground" |
| Hardcoded fill colors on zone backgrounds | Breaks in Dark theme | Use FillTheme: "PanelBackground" instead |
| FontSize 10–11 for values | Default seemed fine | Minimum 13, prefer 14 for operator readability |
| Navigation buttons on content pages | Doesn't know about Header pattern | Page navigation goes in the Header display (Step 10b). Content pages only get in-page action buttons |
...
| Display Action | Tool Call |
|---|---|
| Get display schema | get_table_schema('DisplaysList') |
| Get Canvas structure | list_elements('Canvas') |
| Get Dashboard structure | list_elements('Dashboard') |
| Get element schema | list_elements('ElementName') |
| Browse all dynamics | list_dynamics() |
| Get specific dynamic schema | list_dynamics('FillColorDynamic') |
| Browse symbols | list_elements('Symbol/HMI') or list_elements('Library') |
| Browse Wizard symbols | list_elements('Wizard') |
| Browse theme colors | list_elements('ThemeColors') |
| Read existing display | get_objects('DisplaysList', names=['PageName'], detail='full') |
| Write display | write_objects('DisplaysList', data=[...]) |
| Read layout (for Header name) | get_objects('DisplaysLayouts', names=['Startup'], detail='full') |
| Read Header for nav buttons | get_objects('DisplaysList', names=['Header'], detail='full') |
| Navigate Designer to display | designer_action('navigate', 'Display.PageName') |
| Browse runtime properties | browse_runtime_properties('Client') |
...