• Overview
  • Documentation
  • GitHub
  • Blog
  • Premium support
  • Contact

Nodes

Nodes are defined like this

Grid {
}

They can have children

StackPanel {
    TextBlock { "Hello, World!" }
}

Node can have a name (compiled as x:Name)

Grid "MainGrid" {
}

And a key (compiled as x:Key)

Grid Key="GridKey" {}

Root node requires a name

Window "MyApp.MainWindow" {
}

Name is a fully qualified name of a class generated for this control.

Previous sample compiles to something like this

<Window x:Class="MyApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Window>

Properties

Properties can be defined on a same line

Width: 100, Height: 50, HorizontalAlignment: Right

Or separate lines

Width: 100,
Height: 100 
HorizontalAlignment: Right

Note that comma is optional

Property values

Any value that is correct in XAML is also correct with Ammy

Width: "100" 
Width: "{Binding Width}"
Width: "{StaticResource WidthValue}"

While using string values makes it easier to convert from XAML code, it doesn’t provide you with intellisense or special syntax coloring. It also requires more characters than Ammy’s builtin syntax.

Same properties could be rewritten as

Width: 100
Width: bind Width
Width: resource "WidthValue"

Strings

String values use double quotes

TextBlock { Text: "Hello, World!" }

String value can be used as a content for Node

TextBlock { "Hello, World!" }

And here is the XAML that is generated

<TextBlock>Hello, World!</TextBlock>

Integers

Width: 10
Width: 10.5
Width: "10"
Width: "10.5"

Boolean, x:Null

IsEnabled: true
IsEnabled: false
Text: null

Enum values

HorizontalAlignment: Center
HorizontalAlignment: "Center"

x:Type

Referencing a type is a simple

TargetType: TextBlock

This will generate your usual XAML

TargetType="{x:Type TextBlock}"

Note that you can still use XAML markup extensions with Ammy

TargetType: "{x:Type TextBlock}"

Event handlers

MouseDown: OnMouseDown
MouseDown: "OnMouseDown"

Node values

You can assign Node as a value

ItemTemplate: DataTemplate {
      
}

This would be similar to this XAML

<Node.ItemTemplate>
  <DataTemplate></DataTemplate>
</Node.ItemTemplate>

Array values

Arrays use square brackets as in JSON and lots of other languages

RowDefinitions: [
  RowDefinition { Height: 30 }
  RowDefinition { }
  RowDefinition { Height: "*" }
]

Parameters

You can reference a variable

FontSize: $normalFontSize

or mixin parameter

mixin MyTextBlock (text) {
  TextBlock {
    Text: $text
  }
}

Resources

To reference a static resources use resource keyword

FontFamily: resource "AppFontFamily"

dyn suffix makes it a {DymamicResource ...}

Background: resource dyn "ButtonBackground"

Binding

By default binding has DataContext as a source, so you can just write

Text: bind Text

You can specify binding source with from keyword, like this:

Text: bind Text from $viewmodel

There are many sources available:

Ammy Xaml
$viewmodel default behaviour
$this RelativeSource={RelativeSource Self}
$template RelativeSource={RelativeSource TemplatedParent}
$ancestor<TextBlock>(3) where ancestor level (3) is optional RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBlock}, AncestorLevel=3}
$previous RelativeSource={RelativeSource PreviousData}
"myTextBlock" ElementName=myTextBlock
SomeType.StaticProperty Source={x:Static ns:SomeType.StaticProperty}
$resource SomeResource Source={StaticResource SomeResource}

If you need to manually specify binding properties use set keyword

Text: bind Text 
      set [
          BindingGroupName: "BindingGroup1"
          BindsDirectlyToSource: true
          ConverterCulture: "en-us"
          ConverterParameter: 5
          FallbackValue: 50
          IsAsync: false
          Mode: OneWay
          NotifyOnSourceUpdated: true
          NotifyOnTargetUpdated: false
          NotifyOnValidationError: true
          StringFormat: ""
          TargetNullValue: 0
          UpdateSourceTrigger: PropertyChanged
          ValidatesOnDataErrors: true
          ValidatesOnExceptions: false
          XPath: ""
     ]

Othwerwise there is still good old XAML markup extension

Text: "{Binding Text}"

Omit path entirely if you want to bind to entire DataContext:

TextBlock { 
  Text: bind
}

Binding Converters

Ammy allows you to define inline binding converters that look like this:

TextBlock { 
  Text: bind Name
        convert (string name) => "Hello, " + name
}

Converter is a C# expression following convert keyword. It looks like a lambda expression and behaves as such.
Left side of converter defines binding parameter and its type. This parameter comes from bind expression one line above it. Right side is an expression itself.

Ammy binding expression supports most of C# expression features, but it’s still a subset of it. We support method calls, property access, constructors, arithmetic and comparison operators.

Methods calls:

Text: bind PersonAge
      convert (int age) => Utils.GetDateOfBirth(age)

Property access and operators:

Text: bind Name
      convert (string name) => "This name is " + name.Length + " letters long"

Ternary operator:

Visibility: bind ActualWidth from "LayoutGrid"
            convert (double width) => width > 500 ? Visibility.Visible : Visibility.Hidden

Using

Sometimes you need to import a namespace, like you did with XAML:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:views="clr-namespace:Ammy.Workbench.Views">
  <views:UserControl1></views:UserControl1>
</Window>

Ammy has “using” syntax very simalar to what you would see in C#:

using Ammy.WpfTest
 
Window "MyApp.MainWindow" {
  Workbench {
    
  }
}

Note, that you don’t have to use any prefixes, Ammy resolves them automatically. In case you don’t want to import whole namespace or there is an ambiguity between types, you can also use fully qualified name:

Window "MyApp.MainWindow" {
  Ammy.WpfTest.Workbench {
  }
}

Variables

Ammy allows you to define Variables

$fontColor = "#363636"

and use them

TextBlock {
  Foreground: $fontColor
}

Variables can be defined globally

// MyControl1.ammy
$normalFontSize = "14"
 
UserControl "MyControl1" {
}
// MyControl2.ammy
UserControl "MyControl2" {
  TextBlock { FontSize: $normalFontSize }
}

Or locally

// MyControl1.ammy
UserControl "MyControl1" {
  $normalFontSize = "14"
}
// MyControl2.ammy
UserControl "MyControl2" {
  TextBlock { FontSize: $normalFontSize } // Error!
}

Variable only allows string as a value

$myColor1 = Red // Compilation error!
$myColor2 = "Red" // OK!

Mixins

You can think of mixin as a function that takes zero or more arguments and returns properties.
Simple mixin looks like this:

mixin Centered() for TextBlock {
  TextAlignment: Center
}

This mixin can be applied to any TextBlock and will insert “TextAlignment” property with value set to “Center”.
Mixin application looks like this:

TextBlock {
  #Centered()
}

Although mixins usually return properties, they can also return nodes:

mixin DefaultItem() for ComboBox {
  ComboBoxItem {
    "Select quantity"
  }
}
 
ComboBox {
    #DefaultItem()
}

As was mentioned before, mixins can take parameters:

mixin TwoRows(height1, height2) for Grid {
  RowDefinitions: [
    RowDefinition { Height: $height1 }
    RowDefinition { Height: $height2 }
  ]
}

This mixin applied to a Grid would add two rows:

Grid {
  #TwoRows(25, "*")
}

Parameters can have default values:

mixin SeeThrough(opacity=0.5) for UIElement {
  Opacity: $opacity
}
 
Button {
  #SeeThrough()
}

There is a special value ‘none’ that removes a property:

mixin Cell(row=none, column=none, rowSpan=none, columnSpan=none) for FrameworkElement {
  Grid.Row: $row
  Grid.Column: $column
  Grid.RowSpan: $rowSpan
  Grid.ColumnSpan: $columnSpan
}
 
Border {
  #Cell(rowSpan: 3)
}

This will only insert “Grid.RowSpan” property and nothing else.

Combine keyword

Imagine if we wanted to create a mixin that adds a certain Trigger to style:

mixin RedWhenDisabled() for Style {
  Triggers: [
    Trigger {
      Property: "IsEnabled"
      Value: "False"
 
      Setter {
        Property: "Background"
        Value: "Red" 
      }
    }
  ]
} 
 
Style {
  TargetType: TextBox
  #RedWhenDisabled()
}

All is well, until you decide that TextBoxes should have 0.5 transparency and only be completely opaque when they are focused.

mixin OpaqueWhenFocused() for Style {
  Setter {
    Property: "Opacity"
    Value: "0.5"
  }
  Triggers: [
    Trigger {
      Property: "IsFocused"
      Value: "True"
 
      Setter {
        Property: "Opacity"
        Value: 1 
      }
    }
  ]
}

But after you combine the two:

Style {
  TargetType: TextBox
  #RedWhenDisabled()
  #OpaqueWhenFocused()
}

There is an error generated, since Triggers property is assigned multiple times with different values. This is where combine keyword comes in. If we prepend every Triggers assignment with combine, resulting node will have only one assignment with all of our elements

mixin RedWhenDisabled () for Style {
  combine Triggers: [
    //...
  ]
}
 
mixin OpaqueWhenFocused () for Style {
  combine Triggers: [
    //...
  ]
}

In this case there is no error and both of our triggers are added.

In fact “ThreeRows” mixin from lib.ammy is defined like this:

mixin ThreeRows(height1, height2, height3) for Grid {
  #TwoRows($height1, $height2)
  combine RowDefinitions: [
    RowDefinition { Height: $height3 }
  ]
}

Use can also apply `combine` keyword to Node values:

mixin Red() for Grid {
  Style: combine Style { 
    TargetType: Panel
    #Setter("Background", "Red")
  }
}
 
mixin SeeThrough() for Grid {
  Style: combine Style { 
    TargetType: Panel
    #Setter("Opacity", 0.5)
  }
}
 
UserControl "WpfApplication1.NewControl1" {
  Grid { 
    #Red
    #SeeThrough
  }
}

Aliases

Alias is very close to mixin as it behaves like a function too. But there is an important distinction, alias always returns a node, not property list.

alias Header(text) {
  TextBlock {
    FontSize: 18
    Text: $text
  }
}

Alias invocation is also a bit different

StackPanel {
  @Header("First chapter") {
  }
}

Notice that alias invocation includes curly braces. This is because alias is basically another name for a node with some properties already set. This means that you can use it like any other node:

@Header("Second chapter") {
  TextAlignment: Center
  ToolTip: "Spoiler alert: not everyone comes out alive..."
}

Alias has the same parameter syntax as mixins. Everything applies: default values, named parameters, none etc.

As an example, this is one of the aliases already included in lib.ammy

alias ImageCached(source) {
  Image {
    Source: BitmapImage {
      UriCachePolicy: "Revalidate"
      UriSource: $source
    }
  }
}
 
@ImageCached("http://www.ammyui.com/rick.jpg") {
  Width: 50
}

This inserts a cached image into parent control. Meaning that if your image source is network/web based you won’t have to load it every time.

  • Overview
  • WPF Quickstart
  • Xamarin Forms Quickstart
  • UWP Quickstart
  • How to’s
  • Syntax
    • Nodes
    • Properties
    • Property values
      • Strings
      • Integers
      • Boolean, x:Null
      • Enum values
      • x:Type
      • Event handlers
      • Node values
      • Array values
      • Parameters
      • Resources
      • Binding
    • Using
    • Variables
    • Mixins
      • Combine keyword
    • Aliases