title: "Display Construction — Canvas, Dashboard, Navigation & Symbols" tags: [display, canvas, dashboard, symbol, navigation, hmi, ui, layout] description: "Build displays from scratch: Canvas and Dashboard layouts, symbol placement, asset navigation, CodeBehind, and ActionDynamic click handling" version: "1.0" author: "Tatsoft"
...
Build complete FrameworX displays: choose between Canvas and Dashboard layouts, place controls and symbols with proper tag binding, wire up ActionDynamic for navigation, configure CodeBehind for display lifecycle events, and set up asset-driven navigation patterns.
Use when:
Do NOT use when:
get_objects + write_objects)list_elements)| Category | Items |
|---|---|
| Tools | get_table_schema, write_objects, get_objects, list_elements, list_dynamics |
| Tables | DisplaysList, DisplaysSymbols, DisplaysLayouts |
Critical: Use DisplaysList for creating and editing displays. DisplaysDraw is the Designer visual editor UI — NOT a writable table.
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.
Query element schemas for available controls and their properties:
list_elements('TextBlock') -- specific control schema
list_elements('CircularGauge') -- specific control schema
list_elements('Shapes') -- all drawing primitives (Canvas only)
list_elements('Interaction') -- buttons, inputs, toggles
list_elements('Charts') -- TrendChart, BarChart, PieChart
list_elements('Gauges') -- CircularGauge, LinearGauge, BulletGauge
list_elements('Symbol/HMI/Labels') -- browse symbol library folder
Batch lookups with comma-separated names:
list_elements('TrendChart,CircularGauge,TextBlock')
Canvas elements require Left, Top, Width, Height:
{
"Type": "TextBlock",
"Text": "Tank Level",
"Left": 100, "Top": 50, "Width": 200, "Height": 30
}
Dashboard elements use Column, Row, ColumnSpan, RowSpan:
{
"Type": "TextBlock",
"Text": "Tank Level",
"Column": 0, "Row": 0, "ColumnSpan": 2, "RowSpan": 1
}
Bind element properties to runtime tags using @Tag. prefix:
{
"Type": "TextBlock",
"Text": "@Tag.Plant/Tank1/Level.Value",
"Foreground": "#FFFFFF"
}
Browse available symbols:
list_elements('Symbol') -- all symbol categories
list_elements('Symbol/HMI') -- HMI symbol library
list_elements('Symbol/HMI/Equipment') -- equipment symbols
list_elements('Library') -- all library folders
Symbol placement pattern:
{
"Type": "Symbol",
"SymbolName": "HMI/Equipment/Pump",
"Left": 200, "Top": 300, "Width": 80, "Height": 80,
"SymbolLabels": [
{ "Key": "State", "Value": "@Tag.Plant/Pump1.Running" },
{ "Key": "Speed", "Value": "@Tag.Plant/Pump1.Speed" }
]
}
CRITICAL: Use @Tag. bindings in SymbolLabels. NEVER use @Label. — that prefix is only for symbol internal definitions (in DisplaysSymbols).
WizardSymbols — always available, no import needed: TANK, VALVE, PUMP, MOTOR, BLOWER. These provide easy customization through SymbolLabels for common industrial equipment.
Library symbols (~1,600 available) auto-import when referenced by SymbolName. No manual import step needed — just reference the path.
For button/navigation behavior:
list_dynamics('ActionDynamic')
ActionDynamic supports two formats:
Simple format — single action, auto-mapped to MouseLeftButtonDown:
{
"Type": "Button",
"Text": "Go to Details",
"ActionDynamic": {
"Type": "NavigateToDisplay",
"DisplayName": "DetailPage"
}
}
Nested format — multi-event, explicit mouse event mapping:
{
"ActionDynamic": [
{
"Event": "MouseLeftButtonDown",
"Actions": [
{ "Type": "NavigateToDisplay", "DisplayName": "DetailPage" }
]
}
]
}
Common pattern: navigation button = ActionDynamic + ShineDynamic for visual click feedback.
For all available dynamics:
list_dynamics() -- lists all dynamic types
Displays are document objects — full replacement on write.
Creating a new display:
{
"table_type": "DisplaysList",
"data": [{
"ObjectName": "OverviewPage",
"PanelType": "Dashboard",
"Columns": 4,
"Rows": 3,
"Content": [
{
"Type": "TextBlock",
"Text": "Plant Overview",
"Column": 0, "Row": 0, "ColumnSpan": 4, "FontSize": 24
},
{
"Type": "CircularGauge",
"Value": "@Tag.Plant/Tank1/Level.Value",
"Column": 0, "Row": 1
}
]
}]
}
Modifying an existing display — read first, modify, write back:
get_objects('DisplaysList', names=['MainPage'])
Then modify the content and write the full object.
MainPage is predefined and MCP-labeled in new solutions. Write main content directly into it — no need to create a new display for the landing screen.
Layout regions: Header, Footer, Menu, Submenu, Content.
get_table_schema('DisplaysLayouts')
get_objects('DisplaysLayouts', names=['Startup'])
The Startup layout defines which display loads into the Content region (typically MainPage). Only modify if you need:
Layouts are document objects — read first, modify, write back.
Dependencies: DisplaysSymbols → DisplaysList → DisplaysLayouts
CodeBehind is CLIENT-SIDE C# code embedded in each display for lifecycle events and operator interaction:
DisplayOpening() — runs once when the display loadsDisplayIsOpen() — runs cyclically while displayedDisplayClosing() — runs once when the display closesUse 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.
Common pattern for plant-wide navigation with dynamic content:
Architecture:
Client.Context updates → content displays reactStatic 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.
Plant/Line1/Motor1, Plant/Line2/Motor1 with same member names)Asset() syntax in display bindings for dynamic resolutionbrowse_runtime_properties('Client') at design time to explore the Client namespace and Context propertiesWhen 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.
| 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 explicitly |
Using @Label. in display elements | Confused with symbol definitions | @Label. is only for DisplaysSymbols internals |
| 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: ''} |
| Sending partial display content | Forget it's a document object | Always read-modify-write for displays |
| Canvas positioning in Dashboard | Mixing layout paradigms | Canvas uses Left/Top; Dashboard uses Column/Row |
| Referencing symbols with wrong path | Incomplete library path | Browse with list_elements('Symbol/HMI') to get exact paths |
| Display Action | Tool Call |
|---|---|
| Get display schema | get_table_schema('DisplaysList') |
| Get element schema | list_elements('ElementName') |
| Browse symbols | list_elements('Symbol/HMI') or list_elements('Library') |
| Get dynamics schema | list_dynamics('ActionDynamic') |
| Read existing display | get_objects('DisplaysList', names=['PageName']) |
| Write display | write_objects('DisplaysList', data=[...]) |
| Navigate Designer to display | designer_action('navigate', 'Displays', 'Draw', 'PageName') |