---
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, CodeBehind for display logic, and asset-driven navigation"
version: "3.0"
author: "Tatsoft"
---
...
??????????????????????????????????????????????????????????????????
? PAGE TITLE BAR (x:0, y:0, w:1366, h:50) ACTION NAV BTNBTNs ?
??????????????????????????????????????????????????????????????????
? 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+) ?
??????????????????????????????????????????????????????????????????
Note: Page-to-page navigation buttons belong in the Header display (see Step 10b), not on the Canvas title bar. ACTION BTNs here are for in-page actions only (e.g., start/stop, acknowledge, reset).
Zone sizing rules:
...
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
...
...
???? 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 ?
???????????????????????????????????????????????
...
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) ?
??????????????????????????????????????????????????
...
...
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:
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.
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:
get_objects('DisplaysLayouts', names=['Startup'], detail='full')
get_objects('DisplaysList', names=['Header'], detail='full')
{
"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"
}
}
Button placement in the Header:
CRITICAL: 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 Rectangles...
| 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. 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 |
| Page navigation buttons on content pages | Seems convenient per-page | Navigation goes in the Header display (Step 10b) — content pages only have 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=[...]) |
| Navigate Designer to display | designer_action('navigate', 'Display.PageName') |
| Browse runtime properties | browse_runtime_properties('Client') |
| Read Header for nav buttons | get_objects('DisplaysList', names=['Header'], detail='full') |
| Read layout structure | get_objects('DisplaysLayouts', names=['Startup'], detail='full') |
search_docs('scripts', labels='skill')search_docs('new solution', labels='skill')list_protocols() to browse, then search_docs('modbus', labels='connector') for protocol-specific docssearch_docs('custom symbols', labels='tutorial') for building reusable parameterized symbols