☰ Brand

Persian Programmer

Persian Programmer ×
Home About Us Services and Pricing Contact Us My Resume Co-worker

Introducing C# Markup for Xamarin.Forms

Introducing C# Markup for Xamarin.Forms

This is a guest blog by Vincent Hoogendoorn. Vincent is a hands-on .NET architect, Xamarin veteran, CSharpForMarkup author, contributor of the C# Markup feature in Xamarin.Forms and co-founder of the Dutch Mobile .NET Developers meetup. Vincent works as Architect & Lead Mobile at Anywhere365.

C# Markup

Xamarin.Forms 4.6 introduced C# Markup, a set of fluent helpers and classes that aim to make UI development in C# a joy.

C# Markup helps developers write concise declarative UI markup and cleanly separate it from UI logic, all in C#. Developers get to enjoy C#’s first-class IDE support when writing markup. A single language for markup and logic reduces friction, markup scattering and cognitive load; there is less or no need for language bridging mechanisms like separate converters, styles, resource dictionaries, behaviours, triggers and markup extensions.

Example

Let’s introduce the main features of C# Markup by building this Twitter search page:

The full source of this example can be found here.

Build Top-Down with Hot Reload

C# Markup makes it easy to write markup using a top-down approach – so it reads like a story, filling in details while you progress. This short video shows the process from start to finish in 2.5 minutes (using DebugRainbows and LiveSharp):

Note that this video is unedited and realtime; it was recorded in one go by replaying git commits from the command line.

Write the Page – Like a Story

At the highest level the page contains a header, search results and a footer. So, if we structure the markup top-down – to make it read like a story – the initial markup could be:

SearchPage.cs

using Xamarin.Forms.Markup;

public partial class SearchPage
{
    void Build() => Content = 
        new StackLayout { Children = {
            Header,
            SearchResults,
            Footer
        }};

    StackLayout Header => new StackLayout { };

    CollectionView SearchResults => new CollectionView { };

    Label Footer => new Label { };
}

The void Build() => Content = pattern is a convention that lets you use LiveSharp for stateful hot reload of C# Markup. If you don’t plan on using LiveSharp, omit the Build() method and put the Content assignment in the page constructor.

For now C# Markup is an experimental feature. So we need to set a flag to enable the feature:

App.cs

Device.SetFlags(new string[]{ "Markup_Experimental" });

Next, let’s build out the page components. It is easy as 1-2-3 (and 4 for adding animation):

1 Build the Header – Layout, Binding and Styles

Now let’s create the header. We will use helpers for layout, binding and style:

StackLayout Header => new StackLayout { Children = {
    new Button { Text = "\u1438" } .Style (HeaderButton)
                .Width (50)
                .Bind (nameof(vm.BackCommand)),

    new Entry { Placeholder = "Search" }
               .FillExpandHorizontal ()
               .Bind (nameof(vm.SearchText))
}};

Bind

The Bind helper knows the default bindable property for most built-in view types; Bind‘s target property parameter is optional (you can register a default bindable property for your own / 3rd party view type).

Style

The Style helper refers to an instance of a Style<BindableObject> helper class, e.g.:

Styles.cs

public static class Styles
{
    static Style<Button> headerButton;

    public static Style<Button> HeaderButton => headerButton ??= new Style<Button>(
        (Button.TextColorProperty, Color.CornflowerBlue),
        (Button.FontSizeProperty , 24)
    )   .BasedOn (Implicit.Buttons);
}

2 Build the Search Result – Enums for Grid Rows and Columns

A Grid would be a good choice for the layout of the tweets in the search results. We will use helpers and enums instead of numbers for the rows and columns:

enum TweetRow    { Separator, Title, Body, Actions }
enum TweetColumn { AuthorImage, Content }

CollectionView SearchResults => new CollectionView { ItemTemplate = new DataTemplate(() => 
    new Grid {
        RowDefinitions = Rows.Define (
            (TweetRow.Separator, 2   ),
            (TweetRow.Title    , Auto),
            (TweetRow.Body     , Auto),
            (TweetRow.Actions  , 32  )
        ),

        ColumnDefinitions = Columns.Define (
            (TweetColumn.AuthorImage, 70  ),
            (TweetColumn.Content    , Star)
        ),

        Children = {
            new BoxView { BackgroundColor = Color.Gray }
                         .Row (TweetRow.Separator) .ColumnSpan (All<TweetColumn>()) .Top() .Height (0.5),

            RoundImage ( 53, nameof(Tweet.AuthorImage) )
                        .Row (TweetRow.Title, TweetRow.Actions) .Column (TweetColumn.AuthorImage) .CenterHorizontal () .Top () .Margins (left: 10, top: 4),

            new Label { LineBreakMode = LineBreakMode.MiddleTruncation } .FontSize (16)
                       .Row (TweetRow.Title) .Column (TweetColumn.Content) .Margins (right: 10)
                       .Bind (nameof(Tweet.Header)),

            new Label { } .FontSize (15)
                       .Row (TweetRow.Body) .Column (TweetColumn.Content) .Margins (right: 10)
                       .Bind (Label.FormattedTextProperty, nameof(Tweet.Body), 
                              convert: (List<TextFragment> fragments) => Format(fragments)),

            LikeButton ( nameof(Tweet.IsLikedByMe) )
                        .Row (TweetRow.Actions) .Column (TweetColumn.Content) .Left () .Top () .Size (24)
                        .BindCommand (nameof(vm.LikeCommand), source: vm)
        }
    })}.Background (Color.FromHex("171F2A")) 
       .Bind (nameof(vm.SearchResults));

Bind Converters and Commands

Note that in the above example hw the Bind method enables you to specify inline converters:

new Label { }
           .Bind (Label.FormattedTextProperty, nameof(Tweet.Body), 
                  convert: (List<TextFragment> fragments) => Format(fragments))

Tags : Xamarin C#.Net

0 comments

answer