1. Layout Options
When it comes to designing and laying out the screens of your application, you have two main options, writing code or using XAML. If you've ever done any WPF (Windows Presentation Foundation) or Silverlight development, then you're probably already familiar with XAML. XAML is the eXtensible Application Markup Language that was created to help define an application's look and feel without having to handle it all in code. Xamarin.Forms works with both options. It will ultimately be up to you to decide which option you prefer.
It's important to note that the XAML used for Xamarin.Forms is not compatible with other forms of XAML and XAML tools.
Option 1: Using Code
If you're the type of person that loves to be in the code and wants nothing to do with any sort of markup, or a designer, then you will probably be very comfortable with this option. You programmatically instantiate different types of View
objects and add them directly to a Page
or to a Layout
on a Page
. Here's a simple example of creating a SimplePage
class, instantiating a few View
objects, and adding them to the Page
through a StackLayout
object.
public class SamplePage : ContentPage { public SamplePage() { Padding = new Thickness(20); var label = new Label { Text = "I am a simple page", BackgroundColor = Color.Blue, Font = Font.SystemFontOfSize(30), WidthRequest = 150, HeightRequest = 40 }; var button = new Button { Text = "I have a button", BackgroundColor = Color.Red, Font = Font.SystemFontOfSize( 20 ), WidthRequest = 200, HeightRequest = 200 }; var entry = new Entry { Placeholder = "I have a entry box", BackgroundColor = Color.Green, WidthRequest = 200, HeightRequest = 150 }; Content = new StackLayout { Spacing = 10, Children = {button, entry, label} }; } }
As you can see, the View
objects have a number of the properties in common, which you can use to set the text, colors, spacing, height, width, etc. All you need to do now is modify the GetMainPage
method in the App
class to return a new instance of the SamplePage
class, and away you go.
No one ever accused me of being a designer, but it's this easy to create basic pages in code.
Option 2: Using XAML
If you prefer to separate the look and feel of your application from the logic and implementation, then XAML may just be the way to go. XAML allows you to create the entire layout of your application in a specialized XML format that Xamarin can translate into the pages, layouts, views, and cells, and display them to the user. If you have never used XAML before, it may take a little getting used to. However, once you get the hang of it, it can actually be quite nice.
To use XAML in combination with Xamarin.Forms, you will need to create your project using the Blank App (Xamarin.Forms Portable) template so that all the Xamarin.Forms code can be separated into it's own dll.
In the code example of the previous section, you created a very simple ContentPage
class in code. To create the very same ContentPage
using XAML, right-click the PCL project and select Add > New Item. From the Add New Item dialog box, select the Forms Xaml Page template and replace the default contents with the following:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SampleFormsXAMLApp.SampleXAMLPage" Padding="30"> <StackLayout Spacing="10"> <Label Text="I am a simple Page" BackgroundColor="Blue" Font="30" WidthRequest="150" HeightRequest="40"/> <Button Text="I have a button" BackgroundColor="Red" Font="20" WidthRequest="200" HeightRequest="200"/> <Entry Placeholder="I have a entry box" BackgroundColor="Green" WidthRequest="200" HeightRequest="150"/> </StackLayout> </ContentPage>
If you run your application, you should see the same screen as in the code example. The Page
, Layout
, and View
types map to XML elements and the properties are the element attributes. You are free to use either option to create fully customizable, cross-platform user interfaces.
2. Flexibility through Data Binding
You can create apps where you create the View
objects for your Page
objects and explicitly set their properties, but that quickly becomes cumbersome. When you explicitly set properties in your XAML code, you're no longer able to reuse that XAML Page
for anything else. In other words, you have to create new XAML pages for every variation you need. Who has time for that?
Wouldn't it be nice if you could create reusable XAML pages with no custom user interface code and keep everything logically separated? Of course. Welcome to MVVM.
4. Model-View-ViewModel
Model-View-ViewModel is an architectural pattern that was created with XAML in mind. At its core, it shares the basic concept of other architectural patterns like MVP and MVC. It was designed to separate data, the model layer, from presentation, the view layer. The conduit between the two is the ViewModel. The view model is a class that facilitates communication between the model and view layers through a mechanism known as data binding. Data binding is at the core of the MVVM pattern and is done through XAML itself. Let's take a look at an example.
5. Creating a Sample Application
Start by creating a new Xamarin.Forms application by selecting the Blank App (Xamarin.Forms Portable) project template and giving it the name of MyRecipeBox.
As you have probably guessed, this will be the foundation for a basic app that can store recipes. Let's start by creating a the basic model of the app, a recipe.
In the MyRecipeBox project, create a new folder and name it Models. This isn't a requirement, it just adds some organization to the project which always helps as it gets larger. In the Models folder, add a new class and name it Recipe
. Replace the default implementation with the following:
public class Recipe { public string Name { get; set; } public string Description { get; set; } public TimeSpan PrepTime { get; set; } public TimeSpan CookingTime { get; set; } public List<string> Directions { get; set; } }
Now that you have a basic model class, you can create a view model for it. Think of a view model as a class that contains the parts of a model that need to be shown and interacted with on a screen. To keep things simple, we're going to concentrate on the top four properties.
Create a new folder in the MyRecipeBox project and name it ViewModels. In the ViewModels folder, create a new class and name it RecipeViewModel
. When adopting the MVVM pattern in .NET, ViewModels are typically characterized by the fact that they implement the INotifyPropertyChanged
interface. This interface is what is used to allow other parts of the code to subscribe to events and enable data binding. Replace the default implementation of the RecipeViewModel
class with the following:
public class RecipeViewModel : INotifyPropertyChanged { private Recipe _recipe; public event PropertyChangedEventHandler PropertyChanged; public RecipeViewModel( Recipe recipe ) { _recipe = recipe; Directions = new ObservableCollection<string>(_recipe.Directions); } public ObservableCollection<string> Directions { get; set; } public string Name { get { return _recipe != null ? _recipe.Name : null; } set { if ( _recipe != null ) { _recipe.Name = value; if ( PropertyChanged != null ) { PropertyChanged( this, new PropertyChangedEventArgs( "Name" ) ); } } } } public string Description { get { return _recipe != null ? _recipe.Description : null; } set { if ( _recipe != null ) { _recipe.Description = value; if ( PropertyChanged != null ) { PropertyChanged( this, new PropertyChangedEventArgs( "Description" ) ); } } } } public string PrepTime { get { return _recipe != null ? _recipe.PrepTime.ToString() : "None"; } set { if ( _recipe != null ) { _recipe.PrepTime = TimeSpan.Parse(value); if ( PropertyChanged != null ) { PropertyChanged(this, new PropertyChangedEventArgs("PrepTime")); } } } } public string CookingTime { get { return _recipe != null ? _recipe.CookingTime.ToString() : "None"; } set { if ( _recipe != null ) { _recipe.CookingTime = TimeSpan.Parse(value); if ( PropertyChanged != null ) { PropertyChanged(this, new PropertyChangedEventArgs("CookingTime")); } } } } }
You may have noticed that the RecipeViewModel
implements the INotifyPropertyChanged
interface. If you dig deeper into this interface, you'll see that it contains one property that needs to be implemented.
public interface INotifyPropertyChanged { event PropertyChangedEventHandler PropertyChanged; }
The RecipleViewModel
class takes in an instance of the Recipe
class and then exposes only four of its properties. The getters associated with those properties simply return the data in the Recipe
instance itself. The setters on, the other hand, check to see if PropertyChanged
is not null
. PropertyChanged
will be null
if there are no subscribers to this event. In that case, nothing happens. If PropertyChanged
isn't null
, then the event is called and every subscriber of the event receives the information that this view model has changed.
In the MVVM pattern, the subscriber to these events is typically the view described by the XAML allowing the user interface to update if the underlying models have changed.
It's time to create a page that shows the user the recipe data and leverages data binding to update the user interface. Start by creating a Views folder in the MyRecipeBox project. In the Views folder, add a new Forms Xaml Page and name it RecipeSummaryPage
.
Replace the default XAML in the file with the following:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyRecipeBox.Views.RecipeSummaryPage" Title="{Binding Name}" Padding="30"> <StackLayout Spacing="10" VerticalOptions="FillAndExpand"> <Label Text="Recipe Name" TextColor="Red"/> <Label Text="{Binding Name}" /> <Label Text="Recipe Description" TextColor="Red"/> <Label Text="{Binding Description}"/> <Label Text="Total Prep Time" TextColor="Red"/> <Label Text="{Binding PrepTime}"/> <Label Text="Total Cook Time" TextColor="Red"/> <Label Text="{Binding CookTime}"/> <Label Text="Directions" TextColor="Red"/> <ListView ItemsSource="{Binding Directions}"> </StackLayout> </ContentPage>
As you can see, the binding is created by placing some formatted text where you want the bound data to appear. The syntax to accomplish that is "{Binding xxxxx}"
, where xxxxx
is the name of the property to which you want to bind. Finally, you may be wondering how you tie the view model you created to this view.
If you click the little arrow next to the RecipeSummaryPage.xaml file, you should see another file appear, RecipleSummaryPage.xaml.cs. This is the code behind file that contains the C# code to run this page. You need to modify the constructor of this class to look like this:
public RecipeSummaryPage(RecipeViewModel recipeViewModel) { InitializeComponent(); this.BindingContext = recipeViewModel; }
The BindingContext
property is where you need to assign the view model to create the aforementioned binding. To do that, pass an instance of your RecipeViewModel
into the constructor.
To see the fruits of our labor appear on the screen, you need to make one small change to get this working. In the App.cs file, in the MyRecipeBox project, update the GetMainPage
method as shown below.
public static Page GetMainPage() { var recipe = new Recipe { Name = "Toast", Description = "It's toast, are you kidding?", PrepTime = new TimeSpan( 0, 0, 15 ), CookingTime = new TimeSpan( 0, 2, 0 ), Directions = new List<string>{"Grab bread", "Put bread in toaster", "Eat toast"} }; return new RecipeSummaryPage( new RecipeViewModel( recipe ) ); }
The result should look similar to the following screenshots.
In the next and final step, we'll create and display a list of Recipe
objects that the user can click to bring them to a detail page. Let's start by creating a new view model that contains a list of Recipe
objects. Add a new class to the ViewModels folder and name it RecipeListViewModel
. Its implementation looks like this:
public class RecipeListViewModel { public ObservableCollection<Recipe> Recipes { get; set; } public RecipeListViewModel( ) { Recipes = new ObservableCollection<Recipe>(); Recipes.Add(new Recipe { Name = "Toast", Description = "Are you kidding? It's toast.", CookingTime = new TimeSpan(0, 2, 0), PrepTime = new TimeSpan(0, 0, 15), Directions = new List<string> { "Pick up bread", "Put break in toaster", "Eat Toast" } }); Recipes.Add(new Recipe { Name = "Cereal", Description = "You know, the breakfast stuff.", CookingTime = TimeSpan.Zero, PrepTime = new TimeSpan(0, 1, 0), Directions = new List<string> { "Put cereal in bowl", "Put milk in bowl", "Put spoon in bowl", "Put spoon in mouth" } }); Recipes.Add(new Recipe { Name = "Sandwich", Description = "Bread and stuff. YUM!", CookingTime = TimeSpan.Zero, PrepTime = new TimeSpan(0, 5, 0), Directions = new List<string> { "Get 2 slices of bread", "Put cheese between break slices", "Put ham between break slices", "Enjoy" } }); } }
You may have noticed that we hard coded the recipes in the RecipeListViewModel
class. In a real application, the recipes would be fetched from a web service or a database.
Create a new page to display the list of recipes. In the Views folder, create a new Form Xaml Page and name this one RecipleListPage
. Replace its contents with the following:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyRecipeBox.Views.RecipeListPage" Title="Recipes"> <ListView x:Name="recipeList" ItemsSource="{Binding Recipes}" ItemTapped="OnItemSelected"> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding Name}"/> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
This XAML is quite similar to the previous example. This time, however, you only have a list view on the page. When using data binding in a ListView
, you need to dig down a little deeper to do the actual binding. First, you bind the full list to the ItemsSource
property of theListView
and you then need to define the Template
and DataTemplate
of the ListView
to be a TextCell
and bind that TextCell
to the individual property of the Recipe
instance you want to display. This is what renders the recipe names onto the screen.
You can also see that there's a Name
associated with the ListView
, recipeList
, which will come in handy a bit later, as well as an event handler. In this case, when a user taps an item in the ListView
, the ItemTapped
event is fired. You have now subscribed to that event and will use a method named OnItemSelected
to handle it.
In the next step, we need to do some wiring up in the RecipeListPage.xaml.cs file to set theBindingContext
of our new page as well as implement the OnItemSelected
event handler.
public partial class RecipeListPage { public RecipeListPage() { InitializeComponent(); this.BindingContext = new RecipeListViewModel( ); } public void OnItemSelected(object sender, ItemTappedEventArgs args) { var recipe = args.Item as Recipe; if (recipe == null) return; Navigation.PushAsync(new RecipeSummaryPage(new RecipeViewModel(recipe))); // Reset the selected item recipeList.SelectedItem = null; } }
The BindingContext
property will simply be set to a new instance of the RecipleListViewModel
that you created earlier. The event handler method is a little different. First, you need to check that the selected item is a recipe, which is accomplished in the following lines:
var recipe = args.Item as Recipe; if (recipe == null) return;
If the selected item is a Recipe
object, then you use the Navigation
property to add a new instance of the RecipleSummaryPage
to the current NavigationView
. Finally, you need to make sure that no items in the list are currently selected.
Navigation.PushAsync(new RecipeSummaryPage(new RecipeViewModel(recipe))); // Reset the selected item recipeList.SelectedItem = null;
Accessing the ListView
is done through the Name
that was assigned to it earlier. You can get access to any View
on the page by assigning a Name
to theView
and referring to it by name in the code.
The final change we need to make is updating the GetMainPage
method in the App.cs file. as shown below:
public static Page GetMainPage() { return new NavigationPage(new RecipeListPage()); }
You return a new instance of the NavigationPage
class as your main page and set its root page to a new instance of the RecipleListPage
class. Now that everything is wired up, you can run your app on all three platforms and see something like the following:
Tapping one of the rows in the list takes you to the corresponding recipe summary page as you have seen before.
Conclusion
You have now seen the different options for laying out your application using Xamarin.Forms. You should feel comfortable creating basic applications that can run on the major mobile platforms using a single code base for both the business logic as well as user interface of the application. When you've spent some time working with Xamarin.Forms, the next step will be learning how to customize the application's user interface and add new controls. But that's for another day.
Next Step: Watch the Course
If you'd like to learn more about Xamarin, then check out our course Building Multi-Platform Apps With C# in Xamarin.
In the course, you will learn how to create a cross-platform application from a single code base that will run on three distinctly different platforms: iOS, Android, and Windows Phone 8. Think it can’t be done? In just a little while you will be doing it yourself. Let’s get to work.
You can take the straight away with a completely free 14 day trial of a Tuts+ subscription. Take a look at our subscription options to get started or, if you're just interested in this course, you can buy it individually for $15! Here's a preview to get you started:
Comments