Display Design Best Practices
Best practices and known patterns for creating displays, symbols, and dashboards via MCP.
Table Types
Use DisplaysList for creating and editing displays. DisplaysDraw is the Designer visual editor tab — not a writable table. Similarly, use DisplaysSymbols for symbols. Always call get_table_schema('DisplaysList') for the display schema.
Display vs Symbol Sizing
Displays use Width and Height properties directly on the display object. Symbols use a Size property with format "Width,Height" (e.g., "400,300"). Do not mix them — Size on a display is ignored, Width/Height on a symbol are ignored.
Canvas Positioning
Every element on a Canvas display needs explicit position:
Left— horizontal offset from left edge (pixels)Top— vertical offset from top edge (pixels)
Elements without position stack at (0,0). For a 1920×1080 display, typical layout starts elements at Left=20, Top=20 with spacing.
Dashboard Cell Format
Dashboard displays use a flat Cells array. Each cell needs:
ColumnandRow— zero-based grid positionColumnSpanandRowSpan— how many cells to span (default 1)Elements— array of controls within that cell
{
"Type": "DashboardDisplay",
"Columns": 3,
"Rows": 2,
"Cells": [
{ "Column": 0, "Row": 0, "Elements": [ ... ] },
{ "Column": 1, "Row": 0, "ColumnSpan": 2, "Elements": [ ... ] }
]
}
Do not create CellGroup, Grid, RowDefinition, or ColumnDefinition objects — those are internal WPF representations. The importer builds them from the flat Cells format.
Controls That Cannot Be Written
Some controls are read-only and will be rejected by write_objects. Check the schema — if writeSupported is false, do not include that control. The import will return an error.
Current read-only controls include: Image (use IndustrialIcon or shapes instead), RadioButton, WPF Control, Blazor Control, ToggleButton, MapsESRI, MapsGMap, MapsOSM, FlowPanel.
When a write is rejected, the error message suggests using get_table_schema with the specific control name for alternatives.
IndustrialIcon
Use the IconName property with a human-readable name like "Home", "Valve", "Pump", "Tank", "Motor". Do not set FontFamily, FontSize, or Text — those are internal properties managed by the import pipeline.
Call get_table_schema('IndustrialIcon') to see the full list of available icon names.
LinkedValue — The Universal Data Binding
LinkedValue is the primary data-binding property for almost every control. It accepts tag paths with the @ prefix: "@Tag.FolderPath/TagName". The @ prefix is required to mark the value as a tag reference; a bare "Tag.X" (without @) parses as a literal identifier and fails resolution silently at runtime.
On text-display elements (TextBlock, Label, Button.LabelLink, Button.CaptionLink), LinkedValue also supports composite-template form for labeled values: "Temperature: {@Tag.Reactor/Temp} °C". The @ prefix MUST appear inside the curly braces.
For Button captions, use LabelLink (not Text). Text is design-time fallback only and does NOT interpolate {...} or live-bind @Tag.X at runtime — it renders both as literal text. The importer auto-promotes a non-empty Text to LinkedValue when LinkedValue is empty, but emit LinkedValue directly for clarity.
Exception: BarChart data items use FieldNameValue instead of LinkedValue.
Four-Family Rule for *Link Properties
Every StringWithExpressions-typed property — LinkedValue, ObjectValueLink, VisibleLink, DisableLink, LabelLink, CaptionLink, and every other *Link on dynamics — accepts four value families. Pick by intent.
Family | Form | Example |
|---|---|---|
Numeric literal | Bare digits |
|
Boolean literal | Bare keyword |
|
Tag / expression reference |
|
|
String literal | Escape-wrapped quotes |
|
A bare "Dark" (no @, no quote-wrap) parses as a tag/object reference, fails resolution, and the action silently no-ops with no error logged. The escape-wrap is JSON-source — the on-disk in-memory string is "Dark" (six characters including the quotes); the parser sees the inner quotes and recognizes a string literal.
Composite template form (on LinkedValue, LabelLink, CaptionLink for text-display elements only): "Label: {@Tag.X} unit" — literal text outside braces; @Tag.X inside braces with the @ prefix required.
Dynamics
Dynamics add runtime behavior to any element. Each dynamic is a separate JSON object within the element's Dynamicsarray.
{
"Type": "Ellipse",
"Width": 40,
"Height": 40,
"Dynamics": [
{
"Type": "FillColorDynamic",
"LinkedValue": "@Tag.Process/Status",
"ChangeColorItems": [
{ "Value": "0", "Color": "Gray" },
{ "Value": "1", "Color": "Green" }
]
}
]
}
Key rules:
- Always use
get_table_schemafor the specific dynamic type to see its exact property names ColorDynamicis abstract — useFillColorDynamic,ForeColorDynamic, orStrokeColorDynamicShineDynamiccontrols glow/shine effects — it does not have aLinkedValuepropertyActionDynamicproperties use internal names:ObjectLink,ObjectValueLink,ActionType
Collections (Pens, Columns, Data Items)
Controls with child collections (TrendChart pens, DataGrid columns, PieChart items) use wrapper arrays. The wrapper needs a Type that names the collection class:
{
"Type": "TrendChart",
"Pens": {
"Type": "TrendPenList",
"Items": [
{ "PenName": "Temperature", "TagName": "@Tag.Process/Temp" }
]
}
}
Collection types: TrendPenList, PieChartDataList, GridColumnList, ColumnDataList, TimelineColumnList.
Do not set Id on collection items — it is auto-managed by the engine.
Themes
To apply a theme color, use the "theme:ColorName" prefix on any color property:
{ "Fill": "theme:Accent1", "Foreground": "theme:Text" }
To clear a theme (revert to explicit color), set the color to any explicit value like "#FF0000" — the engine removes the theme binding automatically.
Shapes
All shapes require StrokeThickness (default is usually 1 but should be explicit). Common shapes: Ellipse, Rectangle, Line, Polyline, Path, Border.
Use Fill for interior color, Stroke for border/outline color.
Groups and Containers
Group— groups child elements that move together. Children go inChildElementsarray.Border— a container with optional border and background. Single child inChildproperty.ChildDisplay— embeds another display. UseDisplayNameproperty to reference the target display.
Layouts and Navigation
The MainPage display (ID=0 in DisplaysList) is the default display every solution starts with. Configure it as the main process overview. The Startup layout (ID=0 in DisplaysLayouts) controls which displays appear on startup.
Use get_table_schema('DisplaysLayouts') for layout structure. Layouts define regions; displays fill those regions.
Verification After Creating
After calling write_objects, the success response is your primary confirmation. Do not enter a screenshot verification loop. At most, take one screenshot to confirm visual rendering, then deliver your report and let the user review.
If something looks wrong in a screenshot, mention it in the report — do not silently retry or investigate further.