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.