In the previous post on making fancy layouts with Xamarin Forms we saw how you can design a Dashboard style application that stretches to fill any device size. However one of the challenges of the particular design we chose for Falafel 2 Go was the need to support the concept of an Image Button, where both the icon and the text work as a single control to launch an activity. Xamarin Forms does provide an ImageCell control, which can be used in a TableView to render both text and image as a single control. However, this control is laid out horizontally with the text positioned on the right side of the image as you would expect for a list of items. While we could likely acheive a more precise layout with a custom renderer, we found a much more elegnat, and more importantly, reusable option by instead creating a User Control.
XAML User Controls
The concept of User Controls is not new, and exists in many different platforms including Silverlight, XAML for Windows and Windows Phone as well as Web Forms. Basically the idea is to create a small container for a collection of controls that will be reused as a single control throughout the application. The User Control exposes properties allowing you to reuse the control while allowing each instance of the control to have different settings, layout, or behavior. Because User Controls are so useful (and simple to create) on other platforms, we were pleased and relieved to discover that Xamarin Forms supports them as well. While we weren't able to find any documentation on the matter, with some experimentation we discovered the solution below. If there are official docs for this, please let me know in the comments so I can link to them!
Creating the Xamarin Forms User Control
Begin by adding a regular Xamarin Forms XAML Control to the project. By default this creates a ContentPage. For our layout, we want the image and text to flow vertically, so it makes sense to use a StackLayout, which is why we named the control StackLayoutButton. We can then change the XAML so that the control is also a StackLayout, as shown in the complete example later in the post. In addition, the code-behind class by default doesn't inherit from any base class. Usually you would modify this to be a ContentPage to both match the XAML definition as well as expose the properties and methods of the base control. However, because we're using a StackLayout, we simply needed to update this to inherit from that control. This is also shown later in the full code-behind snippet. By changing the class of the control we can proceed to add additional XAML controls to the StackLayout to make up the overall User Control. In this case we're adding both the Image and Label controls as shown in the complete code below, completing the self-contained StackLayoutButton. Notice that we can define the properties for each control so that they are global to all instances of the button, eliminating the need to set them over and over for each one. But what if we want to vary the properties on a specific control?
Accessing Child Controls and Properties
Because the individual controls are defined inside the StackLayout, we don't have direct access to them from the User Control when we add them to the main page. However, we can workaround this by adding get and set properties to the StackLayoutButton which expose either the individual properties of a control, or the entire control itself. If you review the full code of the StackLayoutButton code-behind below, you see that we've created two additional public properties. The TextColor sets or returns the value of the Label property so it can be set at the UserControl level. The other Label control returns the actual Label itself, so that we could potentially access and modify any property of that control. This is demonstrated in the code-behind for the ActivitesPage control, where we access an instance of the StackLayoutButton by name, and can then access the TextControl property to set the BackgroundColor of that control. Here's what the final example page looks like. We've combined the two options to change both the Font color and the BackgroundColor using the exposed properties for specific buttons. At last, here are the complete code samples for the controls, starting with the XAML for the StackLayoutButton:
<?xml version="1.0" encoding="utf-8" ?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms" xmlns:common="clr-namespace:Falafel2GoV2.Common;assembly=Falafel2GoV2" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Falafel2GoV2.Controls.StackLayoutButton">
<Image x:Name="Icon" Source="{Binding Icon}" />
<Label x:Name="Text" Text="{Binding Title}" HorizontalOptions="Center" LineBreakMode="NoWrap" Font="Small" TextColor="{x:Static common:ColorResources.ListTitle}" />
</StackLayout>
The StackLayoutButton code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Falafel2GoV2.Controls {
public partial class StackLayoutButton : StackLayout {
public Color TextColor { get { return this.Text.TextColor; } set { this.Text.TextColor = value; } }
public Label TextControl { get { return this.Text; } set { this.Text = value; } }
public StackLayoutButton() { InitializeComponent(); } }
}
The ActivitiesView XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:common="clr-namespace:Falafel2GoV2.Common;assembly=Falafel2GoV2" xmlns:controls="clr-namespace:Falafel2GoV2.Controls;assembly=Falafel2GoV2" x:Class="Falafel2GoV2.Views.ActivitiesPage" Title="{Binding ViewName}" BackgroundImage="mainBack.png">
<ContentPage.Content>
<StackLayout Orientation="Vertical" Padding="{x:Static common:PaddingResources.MainBody}">
<Label Text="{Binding ViewName}" Font="42" IsVisible="{Binding IsWindowsPhone}" />
<ActivityIndicator IsRunning="{Binding IsLoading}" IsVisible="{Binding IsLoading}" Color="{x:Static common:ColorResources.ActivityIndicator}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="3*" />
<RowDefinition Height="4*" />
</Grid.RowDefinitions><Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<controls:StackLayoutButton BindingContext="{Binding Blog}" TextColor="Blue" />
<controls:StackLayoutButton x:Name="RedButton" Grid.Column="1" BindingContext="{Binding Training}" TextColor="Red" /> </Grid>< Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<controls:StackLayoutButton BindingContext="{Binding Facebook}" />
<controls:StackLayoutButton Grid.Column="1" BindingContext="{Binding Twitter}" />
<controls:StackLayoutButton Grid.Column="2" BindingContext="{Binding Google}" />
<controls:StackLayoutButton Grid.Row="1" BindingContext="{Binding Eventboard}" />
<controls:StackLayoutButton Grid.Row="1" Grid.Column="1" BindingContext="{Binding Website}" />
<controls:StackLayoutButton Grid.Row="1" Grid.Column="2" BindingContext="{Binding Contact}" />
</Grid>
</Grid>
</StackLayout>
</ContentPage.Content>
</ContentPage>
And finally the ActivitiesView code-behind:
using System;
using System.Collections.Generic;
using Falafel2GoV2;
using Falafel2GoV2.Views;
using Xamarin.Forms;
using Falafel2GoV2.ViewModels;
using Falafel2GoV2.Controls;
using Falafel2GoV2.Models;
namespace Falafel2GoV2.Views {
public partial class ActivitiesPage : ContentPage {
private ActivitiesViewModel vm;
public ActivitiesPage() {
InitializeComponent();
vm = new ActivitiesViewModel();
this.BindingContext = vm;
RedButton.TextControl.BackgroundColor =Color.Black; }
}
}
Wrapping Up and Next Steps
By creating a User Control we were able to eliminate the need to define several copies of controls, encapsulating the controls into a single container so it can be defined with a single line a code. In addition, because we added properties to the control, we allow each instance of the control to have its own properties, and even behaviors. The only thing left is to allow the user to Tap this new custom button to launch the activity. We'll see how easy this is to do in the next post. Until then, as always, I hope this was helpful!
Enjoyed this post and/or found it useful?