Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  1. A DashboardDisplay root with Columns and Rows arrays describing the grid track sizes
  2. A Cells array, where each cell specifies Row, Col, optional RowSpan/ColSpan, a Cell.HeaderLink for the card title, and Content (the element that fills the cell)

...

<ac:structured-macro ac:name="code"> <ac:parameter ac:name="language">json</ac:parameter> ac:plain-text-body{ "Name":

...

"OperationsOverview",

...

"PanelType":

...

"Dashboard",

...

"DashboardDisplay":

...

{

...

"Columns":

...

["*",

...

"*",

...

"*"],

...

"Rows":

...

["Auto",

...

"*",

...

"*"],

...

"Cells":

...

[

...

{

...

"Row":

...

0,

...

"Col":

...

0,

...

"ColSpan":

...

3,

...

"Cell.HeaderLink":

...

"Plant

...

Overview",

...

"Content":

...

{

...

/*

...

header

...

card

...

*/

...

}

...

},

...

{

...

"Row":

...

1,

...

"Col":

...

0,

...

"Cell.HeaderLink":

...

"Production

...

Rate",

...

"Content":

...

{

...

/*

...

gauge

...

*/

...

}

...

},

...

{

...

"Row":

...

1,

...

"Col":

...

1,

...

"Cell.HeaderLink":

...

"Active

...

Alarms",

...

"Content":

...

{

...

/*

...

alarm

...

viewer

...

*/

...

}

...

},

...

{

...

"Row":

...

1,

...

"Col":

...

2,

...

"Cell.HeaderLink":

...

"Shift

...

Output",

...

"Content":

...

{

...

/*

...

KPI

...

*/

...

}

...

}

...

]

...

}

...

}</ac:plain-text-body> </ac:structured-macro>

Track sizing

The Columns and Rows arrays define track sizes. Each entry can be:

...

This is a completely different mental model. If you find yourself calculating Left/Top/Width/Height for Dashboard elements, stop — you're building a Canvas display by accident.

Dashboard-compatible controls only

Not every element type works inside a Dashboard cell. Rule of thumb:

...

Cells size to their content, not the other way around

Setting a row or column to "*" gives that track the space; it does not force the cell's content to fill it. A fixed-size gauge in a "*" row will sit at its native size inside a large cell, not stretch. For content that should fill the cell, use stretch-friendly controls (TrendChart, BarChart, AlarmViewer, DataGrid). Gauges are fixed size; place them in cells sized to match.

Dashboard-compatible controls only

Not every element type works inside a Dashboard cell. Call list_elements('Dashboard') for the authoritative compatibility list in the current release. Rule of thumb:

  • Works in Dashboard: Interaction, Charts, Gauges, Viewer, Editors, TextBlock, Label.
  • Canvas-only: shape primitives, first-class auto-shapes

...

  • , and containers.

Dynamics work in Dashboard for visual controls (FillColorDynamic on a TextBlock, VisibilityDynamic on a chart) but the animation dynamics (Rotate, Scale, MoveDrag, Skew, Bargraph) are Canvas-only — they're silently ignored in Dashboard cells.

...

Six data cards in a 3-column × 2-row grid:

json {

...

"Name":

...

"PlantKPIs",

...

"PanelType":

...

"Dashboard",

...

"DashboardDisplay":

...

{

...

"Columns":

...

["*",

...

"*",

...

"*"],

...

"Rows":

...

["*",

...

"*"],

...

"Cells":

...

[

...

{

...

"Row":

...

0,

...

"Col":

...

0,

...

"Cell.HeaderLink":

...

"Production

...

Rate",

...

"Content":

...

{

...

"Type":

...

"CenterValue",

...

"LinkedValue":

...

"@Tag.Plant/ProductionRate",

...

"CenterTextFormat":

...

"N1",

...

"AccentTextLink":

...

"units/hr"

...

}

...

},

...

{

...

"Row":

...

0,

...

"Col":

...

1,

...

"Cell.HeaderLink":

...

"Active

...

Alarms",

...

"Content":

...

{

...

"Type":

...

"CenterValue",

...

"LinkedValue":

...

"@Tag.Plant/AlarmCount",

...

"CenterTextFormat":

...

"N0",

...

"AccentTextLink":

...

"alarms"

...

}

...

},

...

{

...

"Row":

...

0,

...

"Col":

...

2,

...

"Cell.HeaderLink":

...

"Operator

...

on

...

Duty",

...

"Content":

...

{

...

"Type":

...

"TextBlock",

...

"LinkedValue":

...

"{@Tag.Shift/CurrentOperator}",

...

"FontSize":

...

22,

...

"FontWeight":

...

"Bold"

...

}

...

},

...

{

...

"Row":

...

1,

...

"Col":

...

0,

...

"Cell.HeaderLink":

...

"Temp

...

Trend",

...

"Content":

...

{

...

"Type":

...

"TrendChart",

...

"Duration":

...

"5m",

...

"Pens":

...

[{"Type":"TrendPen","LinkedValue":"@Tag.Plant/AvgTemp","Stroke":"#FFEF4444"}]

...

}

...

},

...

{

...

"Row":

...

1,

...

"Col":

...

1,

...

"Cell.HeaderLink":

...

"Pressure

...

Trend",

...

"Content":

...

{

...

"Type":

...

"TrendChart",

...

"Duration":

...

"5m",

...

"Pens":

...

[{"Type":"TrendPen","LinkedValue":"@Tag.Plant/AvgPressure","Stroke":"#FF38BDF8"}]

...

}

...

},

...

{

...

"Row":

...

1,

...

"Col":

...

2,

...

"Cell.HeaderLink":

...

"Shift

...

Output",

...

"Content":

...

{

...

"Type":

...

"BarChart",

...

"LinkedValue":

...

"@Tag.Shift/OutputByHour"

...

}

...

}

...

]

...

}

...

}

4×3 operator wall

Twelve cells. Full-screen control-room overview with one card per reactor / line / zone:

json "Columns":

...

["*","*","*","*"],

...

"Rows":

...

["*","*","*"]

2-column detail (list → detail)

Asset tree on the left, detail panel on the right:

json {

...

"DashboardDisplay":

...

{

...

"Columns":

...

["240",

...

"*"],

...

"Rows":

...

["Auto",

...

"*"],

...

"Cells":

...

[

...

{

...

"Row":

...

0,

...

"Col":

...

0,

...

"ColSpan":

...

2,

...

"Cell.HeaderLink":

...

"Production

...

Area

...

A",

...

"Content":

...

{

...

"Type":

...

"TextBlock",

...

"LinkedValue":

...

"{@Tag.Site/Name}

...

...

{@Now}"

...

}

...

},

...

{ "Row":

...

1,

...

"Col":

...

0,

...

"Cell.HeaderLink":

...

"Equipment",

...

"Content":

...

{

...

"Type":

...

"AssetsTree"

...

}

...

},

...

{

...

"Row":

...

1,

...

"Col":

...

1,

...

"Cell.HeaderLink":

...

"{@Client.Context.AssetName}",

...

"Content":

...

{

...

"Type":

...

"ChildDisplay",

...

"DisplayLink":

...

"EquipmentDetail"

...

}

...

}

...

]

...

}

...

}

The AssetsTree has all its bindings pre-wired to @Client.Context.* by default — drop it in and navigation "just works."

ColSpan / RowSpan for asymmetric layouts

json {

...

"Row":

...

0,

...

"Col":

...

0,

...

"ColSpan":

...

2,

...

...

...

}

...

//

...

cell

...

spans

...

columns

...

0

...

and

...

1

...

{

...

"Row":

...

1,

...

"Col":

...

0,

...

"RowSpan":

...

2,

...

...

...

}

...

//

...

cell

...

spans

...

rows

...

1

...

and

...

2

Use ColSpan on a header card to stretch it across all grid columns. Use RowSpan when you want a tall element (AlarmViewer, AssetsTree) next to shorter cards.

...

Most dashboard cells end up being trend charts. The canonical setup:

json {

...

"Type":

...

"TrendChart",

...

"Duration":

...

"5m",

...

"YMinValue":

...

0,

...

"YMaxValue":

...

100,

...

"YLabels":

...

5,

...

"XGridLines":

...

6,

...

"YGridLines":

...

5,

...

"LegendPlacement":

...

"BottomPanel",

...

"VerticalCursor":

...

true,

...

"BackgroundTheme":

...

"ControlBackground",

...

"ForegroundTheme":

...

"TextForeground",

...

"BorderBrushTheme":

...

"DefaultBorder",

...

"Pens":

...

[

...

{

...

"Type":

...

"TrendPen",

...

"LinkedValue":

...

"@Tag.Reactor/Temperature_C",

...

"PenLabel":

...

"Temperature",

...

"Stroke":

...

"#FFEF4444",

...

"StrokeThickness":

...

2

...

},

...

{

...

"Type":

...

"TrendPen",

...

"LinkedValue":

...

"@Tag.Reactor/Setpoint",

...

"PenLabel":

...

"Setpoint",

...

"Stroke":

...

"#FF34D399",

...

"StrokeThickness":

...

1

...

}

...

]

...

}

Pens accepts BOTH forms

  • Flat array: "Pens": [ {...}, {...} ] ← prefer this for readability
  • Wrapper object: "Pens": { "Type": "TrendPenList", "Children": [ {...} ] } ← older form, also accepted

...

  1. Create a Client-scope tag typed by your row's UDT. For a reactor list: @Tag.SelectedReactor with UserType: "ReactorRow".
  2. In the DataGrid, set SelectedValuesLink to "@Tag.SelectedReactor". The DataGrid automatically pushes values from the currently-selected row into each matching field of the UDT.
  3. Detail controls on the same display read @Tag.SelectedReactor.Name, @Tag.SelectedReactor.Temperature, etc. They update automatically as the operator clicks rows.

DataGrid definition

json {

...

"Type":

...

"DataGrid",

...

"ItemsSource":

...

"@Dataset.Query.ActiveReactors",

...

"SelectedValuesLink":

...

"@Tag.SelectedReactor",

...

"Columns":

...

{

...

"Type":

...

"GridColumnList",

...

"Children":

...

[

...

{

...

"Type":

...

"GridColumn",

...

"Title":

...

"Reactor",

...

"FieldName":

...

"Name",

...

"Width":

...

120

...

},

...

{

...

"Type":

...

"GridColumn",

...

"Title":

...

"Temp

...

(°C)",

...

"FieldName":

...

"Temperature",

...

"Width":

...

100

...

},

...

{

...

"Type":

...

"GridColumn",

...

"Title":

...

"Pressure",

...

"FieldName":

...

"Pressure",

...

"Width":

...

100

...

},

...

{

...

"Type":

...

"GridColumn",

...

"Title":

...

"Status",

...

"FieldName":

...

"Status",

...

"Width":

...

100

...

} ] }, "BackgroundTheme":

...

"ControlBackground",

...

"ForegroundTheme":

...

"TextForeground",

...

"BorderBrushTheme":

...

"DefaultBorder"

...

}

The detail cells (sibling cells on the same display)

json {

...

"Row":

...

1,

...

"Col":

...

1,

...

"Cell.HeaderLink":

...

"Temperature",

...

"Content":

...

{

...

"Type":

...

"CenterValue",

...

"LinkedValue":

...

"@Tag.SelectedReactor.Temperature",

...

"CenterTextFormat":

...

"N1",

...

"AccentTextLink":

...

"°C"

...

}

...

},

...

<p>{ &quot;Row&quot;: 1, &quot;Col&quot;: 2, &quot;Cell.HeaderLink&quot;: &quot;Pressure&quot;, &quot;Content&quot;: { &quot;Type&quot;: &quot;CenterValue&quot;, &quot;LinkedValue&quot;: &quot;@Tag.SelectedReactor.Pressure&quot;, &quot;CenterTextFormat&quot;: &quot;N1&quot;, &quot;AccentTextLink&quot;: &quot;bar&quot; } },</p> <p>{ &quot;Row&quot;: 2, &quot;Col&quot;: 1, &quot;ColSpan&quot;: 2, &quot;Cell.HeaderLink&quot;: &quot;Status&quot;, &quot;Content&quot;: { &quot;Type&quot;: &quot;TextBlock&quot;, &quot;LinkedValue&quot;: &quot;Status: {@Tag.SelectedReactor.Status}

...

When &quot;, &quot;FontSize&quot;: 16 } }]]&gt;&lt;/ac:plain-text-body&gt; &lt;/ac:structured-macro&gt;</p> <p>When the operator clicks a row in the DataGrid, all three detail cells update simultaneously. No script, no event handler.

Requirements

...

</p> <h3>Requirements</h3> <ul> <li>The UDT's member names must match the DataGrid column

...

<code>FieldName</code>s (case-sensitive).

...

</li> <li>The Client tag must be typed by that UDT.

...

</li> <li>The dataset query must return rows whose column names match the UDT members.

...

</li> </ul> <h2>Section 6 — ComboBox zero-script FK-

...

lookup</h2> <ac:structured-macro ac:name="code"> <ac:parameter ac:name="language">json</ac:parameter> <ac:plain-text-body><![CDATA[{ "Type":

...

"ComboBox",

...

"ItemsSourceType":

...

"DataTable",

...

"ItemsSourceLink":

...

"@Dataset.Query.OperatorsList",

...

"DisplayMember":

...

"FullName",

...

"SelectedValuePath":

...

"OperatorID",

...

"SelectedValueLink":

...

"@Tag.Shift/CurrentOperatorId",

...

"Foreground":

...

"theme:TextForeground",

...

"Background":

...

"theme:ControlBackground",

...

"BorderBrush":

...

"theme:DefaultBorder"

...

}

When the operator picks "Maria Costa" from the dropdown, the OperatorID (say 42) lands in @Tag.Shift/CurrentOperatorId automatically. Other displays binding to that tag update immediately.

...

The AlarmViewer default template ships pre-wired to @Client.AlarmPage.* context tags:

json {

...

"Type":

...

"AlarmViewer",

...

"ShowRowSelectorPane":

...

false,

...

"BackgroundTheme":

...

"ControlBackground",

...

"ForegroundTheme":

...

"TextForeground"

...

}

That's the entire cell content. All alarms, all columns, all features — wired.

Custom filtering

json {

...

"Type":

...

"AlarmViewer",

...

"Filter":

...

"Area

...

=

...

'ReactorZone'",

...

"ShowRowSelectorPane":

...

false

...

}

Filter syntax: "Priority >= 2", "Area = 'Tank1'", or a tag binding "@Tag.AlarmFilter".

Custom columns

json "Columns":

...

{

...

"Type":

...

"GridColumnList",

...

"Children":

...

[

...

{

...

"Type":

...

"GridColumn",

...

"Title":

...

"Active",

...

"FieldName":

...

"ActiveTime_Ticks",

...

"Width":

...

140

...

},

...

{

...

"Type":

...

"GridColumn",

...

"Title":

...

"Tag",

...

"FieldName":

...

"TagName",

...

"Width":

...

200

...

},

...

{

...

"Type":

...

"GridColumn",

...

"Title":

...

"Msg",

...

"FieldName":

...

"Message",

...

"Width":

...

400

...

},

...

{

...

"Type":

...

"GridColumn",

...

"Title":

...

"Pri",

...

"FieldName":

...

"Priority",

...

"Width":

...

50

...

}

...

]

...

}

Available field names: AckStatus, ActiveTime_Ticks, TagName, Group, Value, ID, ItemName, State, AckRequired, Condition, SolutionName, Area, Priority, NormTime_Ticks, AckTime_Ticks, UserName, Message, Duration, Category, DateCreated_Ticks, AuxValue, AlarmLimit, PreviousValue, AuxValue2, AuxValue3. Tick fields format as DateTime at render time.

...

The tree (drop it in, no config)

json {

...

"Type":

...

"AssetsTree"

...

}

The default template already binds:

...

The detail panel — ChildDisplay

json {

...

"Type":

...

"ChildDisplay",

...

"DisplayLink":

...

"EquipmentDetailTemplate"

...

}

Inside EquipmentDetailTemplate, every binding uses Asset(@Client.Context.AssetPath + ".Property") or direct @Tag paths constructed from the context. When the operator clicks a different tree node, the ChildDisplay re-renders with the new asset's data.

Dynamic ChildDisplay

json {

...

"Type":

...

"ChildDisplay",

...

"DisplayLink":

...

"@Tag.DetailDisplayName"

...

}

A Script calculates which detail display to show based on the selected asset's type (Pump → PumpDetail, Reactor → ReactorDetail) and writes to @Tag.DetailDisplayName. The ChildDisplay swaps automatically.

...

  • CarouselAutoCycleLink: 5 for 5-second auto-cycling. Lobby displays, rotating KPI boards.
  • TabControl — no auto-cycle; operator clicks tabs. Drill-down detail panels.

...

<ac:structured-macro ac:name="code"> <ac:parameter ac:name="language">json</ac:parameter> ac:plain-text-body{ "Type":

...

"Carousel",

...

"AutoCycleLink":

...

5,

...

"TabItems":

...

[

...

{

...

"Type":

...

"TabItem",

...

"IsSelected":

...

true,

...

"Header":

...

{

...

"Type":

...

"TextBlock",

...

"LinkedValue":

...

"Production"

...

},

...

"Children":

...

[

...

{

...

"Type":

...

"TrendChart",

...

...

...

}

...

]

...

},

...

{

...

"Type":

...

"TabItem",

...

"Header":

...

{

...

"Type":

...

"TextBlock",

...

"LinkedValue":

...

"Quality"

...

},

...

"Children":

...

[

...

{

...

"Type":

...

"BarChart",

...

...

...

}

...

]

...

},

...

{

...

"Type":

...

"TabItem",

...

"Header":

...

{

...

"Type":

...

"TextBlock",

...

"LinkedValue":

...

"Alarms"

...

},

...

"Children":

...

[

...

{

...

"Type":

...

"AlarmViewer"

...

}

...

]

...

}

...

]

...

}</ac:plain-text-body> </ac:structured-macro>

Rules:

  • Only ONE TabItem should have IsSelected: true
  • Children is an array — can contain multiple elements per tab
  • AutoCycleLink can be a number (seconds) OR a tag binding ("@Tag.ShowroomCycleSeconds") for runtime-configurable cycling

Section 10 — Common KPI card recipes

Big-number CenterValue

json {

...

"Type":

...

"CenterValue",

...

"LinkedValue":

...

"@Tag.Plant/ProductionRate",

...

"CenterTextFormat":

...

"N1",

...

"AccentTextLink":

...

"units/hr",

...

"CenterFontSize":

...

48,

...

"AccentFontSize":

...

14,

...

"BackgroundTheme":

...

"ControlBackground"

...

}

CenterTextFormat: "N0" (integer), "N1" (1 decimal), "N2" (2 decimals), "P1" (percent 1 decimal). AccentTextLink is the unit or caption under/beside the main number.

Composite TextBlock (value + unit + context in one)

json {

...

"Type":

...

"TextBlock",

...

"LinkedValue":

...

"Throughput:

...

{@Tag.Plant/ProductionRate}

...

units/hr

...

({@Tag.Plant/TargetPct}%

...

of

...

target)",

...

"FontSize":

...

16,

...

"FontWeight":

...

"SemiBold",

...

"ForegroundTheme":

...

"TextForeground"

...

}

Threshold-colored value

json {

...

"Type":

...

"TextBlock",

...

"LinkedValue":

...

"{@Tag.Plant/AvgTemp}

...

°C",

...

"FontSize":

...

32,

...

"Dynamics":

...

[

...

{

...

"Type":

...

"TextColorDynamic",

...

"LinkedValue":

...

"@Tag.Plant/AvgTemp",

...

"ChangeColorItems":

...

[

...

{

...

"Type":

...

"ChangeColorItem",

...

"ChangeLimit":

...

0,

...

"LimitColor":

...

"#FF38BDF8"

...

},

...

{

...

"Type":

...

"ChangeColorItem",

...

"ChangeLimit":

...

75,

...

"LimitColor":

...

"#FF34D399"

...

},

...

{

...

"Type":

...

"ChangeColorItem",

...

"ChangeLimit":

...

90,

...

"LimitColor":

...

"#FFF59E0B"

...

},

...

{

...

"Type":

...

"ChangeColorItem",

...

"ChangeLimit":

...

100,

...

"LimitColor":

...

"#FFEF4444"

...

}

...

]

...

}

...

]

...

}

Blue cold → green normal → amber warning → red alarm.

...

Same rule as Canvas: page-to-page navigation belongs in the Header display, not on content pages. Content pages get only in-page interactions.

Tab-bar header pattern

json {

...

"Name":

...

"Header",

...

"PanelType":

...

"Dashboard",

...

"DashboardDisplay":

...

{

...

"Columns":

...

["Auto","Auto","Auto","Auto","*","Auto"],

...

"Rows":

...

["*"],

...

"Cells":

...

[

...

{

...

"Row":

...

0,

...

"Col":

...

0,

...

"Content":

...

{

...

"Type":

...

"Button",

...

"LabelLink":

...

"Overview",

...

"Dynamics":

...

[{"Type":"ActionDynamic","MouseLeftButtonDown":{"Type":"DynamicActionInfo","ActionType":"OpenDisplay","ObjectLink":"OperationsOverview"}}]

...

}

...

},

...

{ "Row":

...

0,

...

"Col":

...

1,

...

"Content":

...

{

...

"Type":

...

"Button",

...

"LabelLink":

...

"Alarms",

...

...

...

}

...

},

...

{

...

"Row":

...

0,

...

"Col":

...

2,

...

"Content":

...

{

...

"Type":

...

"Button",

...

"LabelLink":

...

"Trends",

...

...

...

}

...

},

...

{

...

"Row":

...

0,

...

"Col":

...

3,

...

"Content":

...

{

...

"Type":

...

"Button",

...

"LabelLink":

...

"Reports",

...

...

...

}

...

},

...

{

...

"Row":

...

0,

...

"Col":

...

5,

...

"Content":

...

{

...

"Type":

...

"TextBlock",

...

"LinkedValue":

...

"Logged

...

in:

...

{@Client.Username}"

...

}

...

}

...

]

...

}

...

}

Minimum button size: 100×32. Recommended: 130×40 for touch-friendly control rooms.

...

For DataGrids, a double-click to drill into detail:

json "Dynamics":

...

[

...

{

...

"Type":

...

"ActionDynamic",

...

"MouseDoubleClick":

...

{

...

"Type":

...

"DynamicActionInfo",

...

"ActionType":

...

"OpenDisplay",

...

"ObjectLink":

...

"ReactorDetail"

...

}

...

}

...

]

The target display reads @Tag.SelectedReactor.Name (etc.) — so you get "double-click row → open detail view of that row" with zero code.

Section 12 — Dashboard-specific quirks

  1. Cells size by their content, NOT the other way around. If you set a Row: "*" to expand, but the cell content is a fixed-size gauge, the gauge doesn't stretch — the cell is as tall as the row, but the gauge stays its native size centered in the cell. For content that should fill the cell, use controls with stretch-friendly behavior: TrendChart, BarChart, AlarmViewer, DataGrid. Gauges are a fixed size.
  2. Dynamics that don't work in Dashboard. Silently ignored: RotateDynamic, ScaleDynamic, MoveDragDynamic, SkewDynamic, BargraphDynamic. If you need a rotating element or a custom level-bar, move that cell's content to a Canvas display embedded via ChildDisplay, or pick a control with the behavior built in (LinearGauge for level, symbols for motor-running-spin).
  3. Cell headers use theme styles you can't directly override. Cell.HeaderLink renders in a theme-defined style — you can't set FontSize or Foreground on the header itself from the cell. If you need a custom header, set Cell.HeaderLink to empty string and put a TextBlock as the first element inside the cell Content.
  4. ComboBox + DataTable source loads on first render. Expect a brief empty state on display open. For critical selection flows, bind to @Tag.SelectedValueLink with a pre-populated default.
  5. ChildDisplay depth limit. Don't nest ChildDisplays more than 2 levels deep. Beyond that, performance degrades and context-tag reuse becomes confusing.

Section 13 — Dashboard checklist

  • ? PanelType: "Dashboard" is set
  • ? DashboardDisplay object includes Columns, Rows, Cells
  • ? Every cell has Row, Col, and either Cell.HeaderLink (with content) or omitted for header-less cells
  • ? No Canvas-only element types (Rectangle, Ellipse, Polygon, Cylinder, ShapeGroup, SvgGroup, Group) in Content
  • ? No Canvas-only dynamics (Rotate, Scale, MoveDrag, Skew, Bargraph) on cell content
  • ? Text values ≥ 14 FontSize; big KPIs ≥ 22 FontSize
  • ? ColSpan/RowSpan summed values don't exceed the grid
  • ? AlarmViewer uses default template (no Columns override) unless you NEED custom columns
  • ? DataGrid SelectedValuesLink ↔ UserType-typed Client tag ↔ detail cells binding chain verified
  • ? ComboBox with DataTable source has DisplayMember AND SelectedValuePath AND SelectedValueLink
  • ? TrendChart Pens are flat array form; each pen has at minimum LinkedValue + Stroke
  • ? Header display owns all page-to-page navigation
  • ? get_state after write shows errorList empty

Section 14 — Quick reference

The 10 controls you'll actually use on most Dashboards

TextBlock         - headers, composite KPIs, status text
CenterValue       - big-number KPI tiles
TrendChart        - all time-series (the workhorse)
BarChart          - categorical comparison
AlarmViewer       - alarm list with default template
AssetsTree        - plant navigation sidebar
ChildDisplay      - embedded detail panel
DataGrid          - list in list→detail
ComboBox          - FK selector dropdown
RadialGauge       - circular gauge cells

Canonical dashboard envelope

...

  1. Dynamics that don't work in Dashboard. Silently ignored: RotateDynamic, ScaleDynamic, MoveDragDynamic, SkewDynamic, BargraphDynamic. If you need a rotating element or a custom level-bar, move that cell's content to a Canvas display embedded via ChildDisplay, or pick a control with the behavior built in (LinearGauge for level, symbols for motor-running-spin).
  2. Cell headers use theme styles you can't directly override. Cell.HeaderLink renders in a theme-defined style — you can't set FontSize or Foreground on the header itself from the cell. If you need a custom header, set Cell.HeaderLink to empty string and put a TextBlock as the first element inside the cell Content.
  3. ComboBox + DataTable source loads on first render. Expect a brief empty state on display open. For critical selection flows, bind to @Tag.SelectedValueLink with a pre-populated default.
  4. ChildDisplay depth limit. Don't nest ChildDisplays more than 2 levels deep. Beyond that, performance degrades and context-tag reuse becomes confusing.

Section 13 — Dashboard checklist

  • ? PanelType: "Dashboard" is set
  • ? DashboardDisplay object includes Columns, Rows, Cells
  • ? Every cell has Row and Col; Cell.HeaderLink is either a non-empty string or absent
  • ? No Canvas-only element types (Rectangle, Ellipse, Polygon, Cylinder, ShapeGroup, SvgGroup, Group) in Content
  • ? No Canvas-only dynamics (Rotate, Scale, MoveDrag, Skew, Bargraph) on cell content
  • ? Text values ≥ 14 FontSize; big KPIs ≥ 22 FontSize
  • ? ColSpan/RowSpan summed values don't exceed the grid
  • ? AlarmViewer uses default template (no Columns override) unless you NEED custom columns
  • ? DataGrid SelectedValuesLink ↔ UserType-typed Client tag ↔ detail cells binding chain verified
  • ? ComboBox with DataTable source has DisplayMember AND SelectedValuePath AND SelectedValueLink
  • ? TrendChart Pens are flat array form; each pen has at minimum LinkedValue + Stroke
  • ? Header display owns all page-to-page navigation
  • ? get_state after write shows errorList empty

Section 14 — Quick reference

The 10 controls you'll actually use on most Dashboards

text TextBlock // headers, composite KPIs, status text CenterValue // big-number KPI tiles TrendChart // all time-series (the workhorse) BarChart // categorical comparison AlarmViewer // alarm list with default template AssetsTree // plant navigation sidebar ChildDisplay // embedded detail panel DataGrid // list in list-to-detail ComboBox // FK selector dropdown RadialGauge // circular gauge cells

Canonical dashboard envelope