Skip to main content

User Interface

The GHIElectronics.TinyCLR.UI library provides a WPF-inspired UI framework — windows, panels, layouts, controls, an event-driven dispatcher, and styling via brushes. If you've written Windows Presentation Foundation code, the patterns will feel familiar.

NuGet packages: GHIElectronics.TinyCLR.UI and GHIElectronics.TinyCLR.UI.Media for the framework; whichever display and bus packages your panel needs (Displays → Native or Virtual).

Application skeleton

A UI program subclasses Application and hands it a configured DisplayController. The Application runs the dispatcher loop, wires touch/button input, and renders elements when invalidated.

using GHIElectronics.TinyCLR.Devices.Display;
using GHIElectronics.TinyCLR.UI;

class Program : Application {
public Program(DisplayController d) : base(d) { }

static void Main() {
var display = DisplayController.GetDefault();
display.SetConfiguration(new ParallelDisplayControllerSettings { /* see Graphics page */ });
display.Enable();

var app = new Program(display);
app.Run(CreateWindow(display));
}

private static Window CreateWindow(DisplayController display) {
var window = new Window { /* configure */ };
return window;
}
}

For the display setup itself (timing parameters, backlight, SPI driver wiring), follow the patterns on the Graphics page — the UI library uses the same display backends.

Windows

Every UI application needs at least one Window. A window's Background accepts a Brush — solid color, gradient, image, etc. The example below targets the SCM20260D Dev Board with the 4.3" parallel display and shows a window with a linear-gradient background.

using GHIElectronics.TinyCLR.Devices.Display;
using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Pins;
using GHIElectronics.TinyCLR.UI;
using GHIElectronics.TinyCLR.UI.Media;
using System.Drawing;

class Program : Application {
static Program app;

public Program(DisplayController d) : base(d) { }

static void Main() {
var backlight = GpioController.GetDefault().OpenPin(SC20260.GpioPin.PA15);
backlight.SetDriveMode(GpioPinDriveMode.Output);
backlight.Write(GpioPinValue.High);

var display = DisplayController.GetDefault();
display.SetConfiguration(new ParallelDisplayControllerSettings {
Width = 480, Height = 272,
DataFormat = DisplayDataFormat.Rgb565,
Orientation = DisplayOrientation.Degrees0,
PixelClockRate = 10_000_000,
HorizontalFrontPorch = 2, HorizontalBackPorch = 2,
HorizontalSyncPulseWidth = 41, HorizontalSyncPolarity = false,
VerticalFrontPorch = 2, VerticalBackPorch = 2,
VerticalSyncPulseWidth = 10, VerticalSyncPolarity = false,
PixelPolarity = false, DataEnablePolarity = false, DataEnableIsFixed = false,
});
display.Enable();

app = new Program(display);
app.Run(CreateWindow(display));
}

private static Window CreateWindow(DisplayController display) {
var window = new Window {
Width = (int)display.ActiveConfiguration.Width,
Height = (int)display.ActiveConfiguration.Height,
Background = new LinearGradientBrush(Colors.Blue, Colors.Teal, 0, 0,
(int)display.ActiveConfiguration.Width, (int)display.ActiveConfiguration.Height),
Visibility = Visibility.Visible,
};
return window;
}
}

For a 7" display, use width 800 × height 480 and the timing values from Displays → 7" display configuration.

For an SPI virtual display (like the ST7735 on the SC20100S Dev Board), the Application is constructed with explicit width/height instead of a DisplayController, and you route flush events to the SPI driver. See Displays → Virtual displays for the SPI display setup; the UI portion becomes:

class Program : Application {
public Program(int width, int height) : base(width, height) { }

static void Main() {
// ... set up st7735 controller (see Graphics → Virtual displays)
Graphics.OnFlushEvent += (sender, data, x, y, w, h, origW) => st7735.DrawBuffer(data);

var app = new Program(160, 128);
app.Run(CreateWindow(160, 128));
}

private static Window CreateWindow(int width, int height) {
var window = new Window {
Width = width,
Height = height,
Background = new LinearGradientBrush(Colors.Blue, Colors.Red, 0, 0, width, height),
Visibility = Visibility.Visible,
};
return window;
}
}

Elements

A Window has a single Child — a UIElement. If you need more than one element on screen, the child is a container (Panel, Canvas, StackPanel) holding the rest. The convention in this page is a private static UIElement Elements() method that builds the tree and returns it; assign it with window.Child = Elements(); in CreateWindow.

All elements live under GHIElectronics.TinyCLR.UI.Controls and descend from UIElement. The rest of this page references the available elements with short construction examples. Each example assumes font is a loaded font resource.

TextBox

Single or multi-line text input.

var textBox = new TextBox {
Text = "Hello World!",
Font = font,
Width = 120,
Height = 25,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};

Panel

A simple container that holds multiple children without any layout rules — positions are determined by each child's alignment and margin properties.

var panel = new Panel();

var txt1 = new TextBox {
Font = font,
Text = "Hello World!",
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
};
txt1.SetMargin(20);

var txt2 = new Text(font, "TinyCLR is Great!") {
ForeColor = Colors.White,
HorizontalAlignment = HorizontalAlignment.Right,
};
txt2.SetMargin(20);

var rect = new Rectangle(200, 10) {
Fill = new SolidColorBrush(Colors.Green),
HorizontalAlignment = HorizontalAlignment.Center,
};

panel.Children.Add(txt1);
panel.Children.Add(txt2);
panel.Children.Add(rect);

StackPanel

Like Panel, but stacks children in order — vertically or horizontally. The stacking direction overrides the corresponding axis of VerticalAlignment / HorizontalAlignment.

var panel = new StackPanel(Orientation.Vertical);
panel.Children.Add(txt1);
panel.Children.Add(txt2);
panel.Children.Add(rect);

Grid

Row/column layout with pixel, Auto, and star (weighted) sizing — a WPF-style subset. Assign children to cells with Grid.SetRow(...) / Grid.SetColumn(...); there is no row/column spanning — each child occupies exactly one cell.

var grid = new Grid();
grid.RowDefinitions.Add(GridLength.Auto());
grid.RowDefinitions.Add(GridLength.Star(1));
grid.ColumnDefinitions.Add(GridLength.Star(2));
grid.ColumnDefinitions.Add(GridLength.Pixel(120));

var title = new Text(font, "Title");
Grid.SetColumn(title, 0);
grid.Children.Add(title);

var button = new Button { Child = new Text(font, "OK") };
Grid.SetRow(button, 1);
Grid.SetColumn(button, 1);
grid.Children.Add(button);

DockPanel

Docks each child to an edge of the space left by the previously docked children — Dock.Left, Top, Right, or Bottom. When LastChildFill is true (the default), the last child fills whatever remains in the center.

var dockPanel = new DockPanel();

var header = new Text(font, "Header") { HorizontalAlignment = HorizontalAlignment.Center };
DockPanel.SetDock(header, Dock.Top);

var sidebar = new Rectangle(80, 200) { Fill = new SolidColorBrush(Colors.Gray) };
DockPanel.SetDock(sidebar, Dock.Left);

var content = new Text(font, "Fills the remaining space");

dockPanel.Children.Add(header);
dockPanel.Children.Add(sidebar);
dockPanel.Children.Add(content); // last child fills the center

Canvas

Pixel-precise child placement. Position with Canvas.SetLeft(...), Canvas.SetTop(...), Canvas.SetRight(...), Canvas.SetBottom(...). Width/Height are requested dimensions; the actual size is constrained by the parent. Read ActualWidth/ActualHeight for the final size.

var canvas = new Canvas();

var txt = new Text(font, "TinyCLR is Great!") { ForeColor = Colors.White };
Canvas.SetLeft(txt, 30);
Canvas.SetBottom(txt, 25);

var rect = new Rectangle(150, 30) {
Fill = new SolidColorBrush(Colors.Green),
HorizontalAlignment = HorizontalAlignment.Center,
};
Canvas.SetLeft(rect, 20);
Canvas.SetBottom(rect, 20);

canvas.Children.Add(rect);
canvas.Children.Add(txt);

Border

A decorative border around a single Child.

var border = new Border();
border.SetBorderThickness(10);
border.BorderBrush = new SolidColorBrush(Colors.Red);

var txt = new TextBox { Font = font, Text = "TinyCLR is Great!" };
border.Child = txt;
tip

If the child doesn't fill the area inside the border, the border thickness will grow to compensate. To prevent that, put the Border inside a Canvas (or any container) and set explicit positions:

var canvas = new Canvas();
Canvas.SetLeft(border, 20);
Canvas.SetTop(border, 20);
canvas.Children.Add(border);

GroupBox

A titled frame around a single child — draws a labeled border with the header above the content.

var groupBox = new GroupBox {
Header = "Settings",
Font = font,
Child = new Text(font, "Inside the group"),
};

Expander

A header with an expand/collapse chevron; tapping the header shows or hides the single child content.

var expander = new Expander {
Header = "Advanced",
Font = font,
Child = new Text(font, "Hidden until expanded"),
IsExpanded = false,
};

Button

Accepts taps (touch screen) or selects (hardware buttons). Needs a Child — typically text — to label the button. Hook the Click event for input.

var label = new Text(font, "Push me!") {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};

var button = new Button {
Child = label,
Width = 100,
Height = 40,
};

button.Click += (sender, e) => Debug.WriteLine("Tapped");

CheckBox

A two-state check box that toggles when tapped (touch or the Select hardware button). It has no inline label — pair it with a Text element for a caption.

var checkBox = new CheckBox();
checkBox.Checked += (sender, e) => Debug.WriteLine("Checked");
checkBox.Unchecked += (sender, e) => Debug.WriteLine("Unchecked");

RadioButton

A selectable button that's mutually exclusive with other RadioButtons sharing the same group name (passed to the constructor). RadioButtonManager.GetValue(groupName) returns the Value of whichever button in the group is currently selected.

var optionA = new RadioButton("options") { Value = "A" };
var optionB = new RadioButton("options") { Value = "B" };

optionA.Click += (sender, e) => Debug.WriteLine("Selected: " + RadioButtonManager.GetValue("options"));

TextFlow

Mixed-run rich text — different colors and fonts in the same paragraph, line breaks, larger blocks than TextBox handles comfortably.

var textFlow = new TextFlow();
textFlow.TextRuns.Add("Hello ", font, Colors.Red);
textFlow.TextRuns.Add("World!", font, Colors.Purple);
textFlow.TextRuns.Add(TextRun.EndOfLine);
textFlow.TextRuns.Add("TinyCLR is Great!", font, Colors.Yellow);

ListBox

A vertical list of selectable items.

var listBox = new ListBox();
listBox.Items.Add(new Text(font, "Item 1"));
listBox.Items.Add(new Text(font, "Item 2"));
listBox.Items.Add(new Text(font, "Item 3"));
listBox.Items.Add(new Text(font, "Item 4"));

To add a non-selectable separator (e.g., a thin line between groups), wrap a Rectangle in a ListBoxItem with IsSelectable = false:

var rect = new Rectangle {
Height = 1, Width = 30,
Stroke = new Pen(Colors.Black),
};

var separator = new ListBoxItem {
Child = rect,
IsSelectable = false,
};
separator.SetMargin(2);

listBox.Items.Add(separator);

ComboBox

A collapsible selection list — shows the selected option and expands to the full list on tap. Inherits from ListBox, so it shares its SelectionChanged event.

var comboBox = new ComboBox {
Font = font,
Width = 120,
Height = 25,
Options = new ArrayList { "Red", "Green", "Blue" },
};

comboBox.SelectionChanged += (sender, e) => Debug.WriteLine("Selected index " + comboBox.SelectedIndex);

ScrollViewer

Wraps content larger than the visible area and provides scrolling — line-by-line or by page.

var scrollViewer = new ScrollViewer {
Background = new SolidColorBrush(Colors.Gray),
ScrollingStyle = ScrollingStyle.LineByLine,
LineWidth = 10,
LineHeight = 10,
};

scrollViewer.TouchUp += (sender, e) => ((ScrollViewer)sender).LineDown();

MessageBox

A modal dialog with a message, caption, and standard buttons (Yes/No, OK/Cancel, etc.).

var messageBox = new MessageBox(font);
messageBox.ButtonClick += (sender, e) => Debug.WriteLine(e.DialogResult.ToString());

messageBox.Show("Confirm action?", "Are you sure?", MessageBox.MessageBoxButtons.YesNo);

Slider

A draggable bar for selecting a numeric value within a range.

var slider = new Slider(30, 150) {
Direction = Orientation.Vertical,
};

slider.OnValueChanged += (sender, e) => Debug.WriteLine("new value = " + e.Value);

ProgressBar

A bar that visually fills toward MaxValue, growing in one of four directions.

var progressBar = new ProgressBar {
Width = 150, Height = 20,
MaxValue = 100,
Value = 40,
Direction = Direction.Right,
};

Gauge

An analog dial with calibrated tick marks, an optional threshold arc around a recommended value, an optional seven-segment digital readout, and a pointer needle. Always square — pass the side length to the constructor.

var gauge = new Gauge(150) {
Font = font,
MinValue = 0,
MaxValue = 100,
Value = 62,
DialText = "RPM x100",
EnableThreshold = true,
RecommendedValue = 80,
};

DataGrid

A simple table with columns and rows. Cells can be tapped via TapCellEvent.

var dataGrid = new DataGrid(width: 400, rowHeight: 60, rowCount: 5, font);

dataGrid.AddColumn(new DataGridColumn("Column 1", 60));
dataGrid.AddColumn(new DataGridColumn("Column 2", 60));
dataGrid.AddColumn(new DataGridColumn("Column 3", 60));

dataGrid.AddItem(new DataGridItem(new[] { "item 1", "item 2", "item 3" }));
dataGrid.AddItem(new DataGridItem(new[] { "item 4", "item 5", "item 6" }));

dataGrid.TapCellEvent += (sender, e) => Debug.WriteLine(e.ToString());

TreeView

A hierarchical list of TreeNodes with expand/collapse. Tapping a node's chevron toggles its children; tapping the row selects it.

var treeView = new TreeView { Font = font };

var root = new TreeNode("Root");
root.Add(new TreeNode("Child 1"));
root.Add(new TreeNode("Child 2"));

treeView.AddNode(root);

Calendar

A month calendar — a header with prev/next arrows, a day-of-week row, and a grid of day cells. Tapping the arrows changes the month; tapping a day selects it.

var calendar = new Calendar {
Font = font,
Year = 2026,
Month = 7,
};

A two-level menu bar: a row of top-level entries; tapping one opens its sub-items as an inline dropdown drawn within the control's bounds. Embedded UIs have no popup layer, so size the Menu tall enough to show the open dropdown.

var menu = new Menu { Font = font };

var file = new MenuEntry("File");
file.Add(new MenuEntry("Open"));
file.Add(new MenuEntry("Save"));
menu.AddItem(file);

menu.ItemClick += (sender, header) => Debug.WriteLine("Clicked: " + header);

TabControl

A tabbed container: a strip of tab headers over a body that shows the selected tab's content.

var tabControl = new TabControl();
tabControl.AddTab("General", new Text(font, "General settings"));
tabControl.AddTab("Advanced", new Text(font, "Advanced settings"));

Chart

Simple plotting with bars, points, or other modes.

var data = new ArrayList();
var random = new Random();
for (var i = 0; i < 10; i++)
data.Add(new DataItem { Value = random.Next(100), Name = "N" + i });

var chart = new Chart(400, 200) {
Font = font,
DivisionAxisX = 1,
DivisionAxisY = 10,
RadiusPoint = 10,
ChartTitle = "TinyCLR Chart",
Items = data,
Mode = ChartMode.RectangleMode,
};

Image

Displays a bitmap loaded from a project image resource, with Stretch.None (natural size) or Stretch.Fill (scaled to Width/Height).

var image = new Image {
Source = BitmapImage.FromGraphics(Graphics.FromImage(Resources.GetBitmap(Resources.BitmapResources.logo))),
Stretch = Stretch.Fill,
Width = 64,
Height = 64,
};

Shapes

GHIElectronics.TinyCLR.UI.Shapes also includes Ellipse, Line, and Polygon — alongside Rectangle (shown above under Panel) — all sharing the Fill and Stroke properties of the Shape base class.

using GHIElectronics.TinyCLR.UI.Shapes;

var ellipse = new Ellipse(xRadius: 40, yRadius: 20) {
Fill = new SolidColorBrush(Colors.Blue),
Stroke = new Pen(Colors.White, 2),
};

var line = new Line(dx: 100, dy: 50) { Stroke = new Pen(Colors.Red, 2) };

var triangle = new Polygon(new[] { 0, 40, 20, 0, 40, 40 }) {
Fill = new SolidColorBrush(Colors.Green),
};

The Dispatcher

The UI runs on a single thread driven by a Dispatcher. All element updates have to happen on that thread — if you change an element from a background thread (a Timer callback, a network reader, an interrupt handler), wrap the update in Application.Current.Dispatcher.Invoke(...).

The example below shows a clock that updates once a second from a System.Threading.Timer:

static void Counter(object state) {
Application.Current.Dispatcher.Invoke(TimeSpan.FromMilliseconds(1), _ => {
var txt = (Text)state;
txt.TextContent = DateTime.Now.ToString();
txt.Invalidate();
return null;
}, null);
}

private static UIElement Elements() {
var txt = new Text(font, "Hello World!") {
ForeColor = Colors.White,
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center,
};

new Timer(Counter, txt, 2000, 1000);
return txt;
}

Or use the built-in DispatcherTimer, which fires its callback on the UI thread so no Invoke is needed:

private static UIElement Elements() {
var txt = new Text(font, "Hello World!") {
ForeColor = Colors.White,
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center,
};

var timer = new DispatcherTimer { Tag = txt, Interval = new TimeSpan(0, 0, 1) };
timer.Tick += (sender, e) => {
var t = (Text)((DispatcherTimer)sender).Tag;
t.TextContent = DateTime.Now.ToString();
t.Invalidate();
};
timer.Start();

return txt;
}

User input

Touch

The UI library consumes touch events through app.InputProvider. Wire your touch screen driver to call:

app.InputProvider.RaiseTouch(x, y, touchState, DateTime.UtcNow);

with the position and state from the driver's TouchMove / TouchUp / TouchDown events.

Hardware buttons

Hardware buttons map to logical roles (Left, Right, Up, Down, Select, Back, Home) so the UI can be navigated without a touch screen. The example below maps GPIO pins to Left and Right for navigating a MessageBox:

using GHIElectronics.TinyCLR.UI.Input;

var buttonLeft = gpioController.OpenPin(SC20100.GpioPin.PE3);
var buttonRight = gpioController.OpenPin(SC20100.GpioPin.PB7);

buttonLeft.SetDriveMode(GpioPinDriveMode.InputPullUp);
buttonRight.SetDriveMode(GpioPinDriveMode.InputPullUp);

buttonLeft.DebounceTimeout = TimeSpan.FromMilliseconds(50);
buttonRight.DebounceTimeout = TimeSpan.FromMilliseconds(50);

buttonLeft.ValueChangedEdge = GpioPinEdge.RisingEdge;
buttonRight.ValueChangedEdge = GpioPinEdge.RisingEdge;

buttonLeft.ValueChanged += (sender, e) =>
Program.MainApp.InputProvider.RaiseButton(HardwareButton.Left, true, DateTime.UtcNow);

buttonRight.ValueChanged += (sender, e) =>
Program.MainApp.InputProvider.RaiseButton(HardwareButton.Right, true, DateTime.UtcNow);

Routing those button events to a focused element uses Buttons.Focus and the Buttons.ButtonUpEvent routed event:

void ShowConfirmation(int counter) {
var messageBox = new MessageBox(font12);
mainStackPanel.Children.Add(messageBox);

messageBox.AddHandler(Buttons.ButtonUpEvent, new RoutedEventHandler(ProcessButtons), true);
messageBox.Show("Counter " + counter + ". Are you sure?", "Confirm", MessageBox.MessageBoxButtons.YesNo);
Buttons.Focus(messageBox);

// Touch route — works in parallel with hardware buttons.
messageBox.ButtonClick += (sender, e) => {
if (e.DialogResult == MessageBox.DialogResult.Yes)
Debug.WriteLine("Yes");
};

void ProcessButtons(object sender, RoutedEventArgs e) {
var args = (ButtonEventArgs)e;
switch (args.Button) {
case HardwareButton.Left:
Debug.WriteLine("Left → Yes");
break;
case HardwareButton.Right:
Debug.WriteLine("Right → No");
break;
case HardwareButton.Select:
Debug.WriteLine("Select");
break;
}
messageBox.Close();
}

mainStackPanel.Invalidate();
}

Visual Studio UI Designer

The TinyCLR-SDK ships a Visual Studio extension (TinyCLR UI Designer, VSIX) that lets you lay out a screen visually instead of writing Elements() by hand. It adds:

  • A .tcui screen file format — an XML, WPF-flavored description of a window's element tree.
  • A live WPF preview and toolbox (Tools/Extensions → TinyCLR UI Preview…) with drag-and-drop placement, a target-Grid picker, and a right-click Edit .tcui properties dialog.
  • MSBuild code generation that turns each .tcui into a sibling .tcui.g.cs partial class before compiling.
  • Project/item templates: TinyCLR UI Application (a full starter project) and TinyCLR UI Screen (adds one more .tcui + code-behind pair to an existing project).

Install it from the Visual Studio Marketplace; it requires the TinyCLR OS Project extension and Visual Studio 2022 (17.x).

The .tcui file

Each screen is one XML file. The root element is either Window or a panel (Canvas, Grid, StackPanel, DockPanel) for a reusable fragment; Class="Namespace.ClassName" names the generated partial class. Element names map to GHIElectronics.TinyCLR.UI.Controls types; a Name attribute becomes a backing field; dotted attributes (Grid.Row, Canvas.Left, DockPanel.Dock) are attached properties.

MainWindow.tcui
<Window Class="MyApp.MainWindow" Width="480" Height="272">
<Grid Name="RootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="120" />
</Grid.ColumnDefinitions>

<Text Name="TitleText" TextContent="Hello TinyCLR UI" Grid.Row="0" Grid.Column="0" />

<Button Name="OkButton" Width="100" Height="40" TabIndex="0" IsTabStop="true"
Grid.Row="1" Grid.Column="1" Click="OkButton_Click">
<Text TextContent="OK" />
</Button>
</Grid>
</Window>

The code-behind partial only needs to call InitializeComponent() and wire any handlers referenced by name in the markup:

MainWindow.cs
using GHIElectronics.TinyCLR.UI;

namespace MyApp {
public partial class MainWindow : Window {
public MainWindow() => InitializeComponent();

private void OkButton_Click(object sender, RoutedEventArgs e) => Debug.WriteLine("OK tapped");
}
}

GenerateTcuiScreens (an MSBuild task from GHIElectronics.TinyCLR.UI.BuildTasks, wired in by GHIElectronics.TinyCLR.UI.targets) reads the XML directly and emits MainWindow.tcui.g.cs with the private void InitializeComponent() body — one new <Type>() plus property/attached-property/event lines per element — before every build. It regenerates only when the .tcui is newer than its output, and is cleaned alongside obj/bin.

Supported elements

The generator recognizes: Grid, Canvas, StackPanel, DockPanel (panels); Button, Border, ScrollViewer, GroupBox, Expander (single-child content controls); Text (also accepts the WPF alias TextBlock), TextBox, TextFlow, CheckBox, RadioButton, ProgressBar, Slider, Gauge, ComboBox, ListBox, DataGrid, Chart, Rectangle, Ellipse, Image, TabControl (<TabItem Header="...">), TreeView (<TreeNode Header="...">, nestable), Menu (<MenuItem Header="...">, nestable), and Calendar. The live-preview toolbox can drag-and-drop a smaller core set directly onto the canvas; every other supported element can still be hand-authored in the XML and generates correctly.

Property, color, and layout conventions

The generator maps WPF-flavored attributes onto whichever real TinyCLR.UI member exists on that element type, so a markup snippet pasted from WPF mostly works as-is:

  • Colors#RGB / #RRGGBB / #AARRGGBB, 0xRRGGBB, the 12 Colors names, or common extended names (Orange, Navy, Crimson, …). Routed to a Brush or a Color property depending on what the control actually exposes (e.g. Background on Border becomes a SolidColorBrush; on Gauge it sets BackColor directly); an #AARRGGBB alpha becomes the brush's Opacity.
  • Grid lengthsAuto, WPF-style * / 2*, or the legacy Pixel:N / Star:N.
  • Margin — one integer (SetMargin(n)) or four comma-separated (SetMargin(l,t,r,b)).
  • Opacity (0–1) — mapped to the integer Alpha (0–255) property on controls that expose one.
  • EventsClick="Handler" etc. emit control.Click += this.Handler;, only for events that actually exist on that element type.
  • Root Background — a solid color, linear:Color1,Color2 for a corner-to-corner LinearGradientBrush, or image:ResourceName for an ImageBrush over a project bitmap resource.

An attribute the device control genuinely has no equivalent for (a WPF-only decoration, or a property on the wrong control type) is dropped with a // note: comment in the generated file rather than breaking the build — check the generated .tcui.g.cs if a property you set doesn't seem to take effect. One current gap: the on-device Grid has no row/column spanning, so Grid.RowSpan/Grid.ColumnSpan are accepted in markup (for the WPF preview) but ignored in codegen.

API reference

NamespaceDescription
GHIElectronics.TinyCLR.UITinyCLR UI framework (windows, controls, events)
GHIElectronics.TinyCLR.Devices.DisplayDisplay controller for rendering UI output