Click here to Skip to main content
15,860,859 members
Articles / Mobile Apps / Xamarin

Creating a Cross-Platform Application Bar for Xamarin Forms with Calcium

Rate me:
Please Sign up or sign in to vote.
4.94/5 (21 votes)
7 Oct 2014CPOL19 min read 89.9K   24   15
Using Xamarin Forms platform specific rendering to create a cross-platform application bar.

Calcium for Xamarin Forms Banner

Introduction

The Windows Phone application bar is a quintessential part of the Windows Phone user experience. I couldn’t contemplate building a Windows Phone app without leveraging the built-in ApplicationBar.

In this article you look at defining a custom AppBar in XAML; binding it to collections of command objects and at directly binding AppBar items to viewmodel properties. You see how to employ platform specific rendering to display an AppBar on iOS, Android, and Windows Phone. You learn how the Calcium AppBar is multi-page aware and can be placed within multiple pages hosted in a CarouselPage or a TabbedPage. You see how to display a custom menu in iOS and Android. Finally, you look at how to register a custom platform-specific view renderer.

Calcium’s AppBar for the Windows Phone platform leverages the Windows Phone SDK’s built-in ApplicationBar to provide binding support and to allow multiple application bars within Pivot and Panorama controls defined using XAML. In addition, Calcium’s AppBar has support for toggle buttons and toggle menu items, navigation buttons and so forth. This goes beyond what you can expect to achieve when creating a native renderer for the Windows Phone SDK’s ApplicationBar control.

NOTE. The AppBar control for Xamarin Forms presented in this article is a work in progress. As far a being production ready, it's probably not quite there yet. 

Before you begin, if you haven’t already, I recommend reading the first article in the series before tackling this one.

Articles in this Series

Source Code for Calcium and Example Apps

The source code for Calcium for Xamarin.Forms and the examples is located at https://calcium.codeplex.com/SourceControl/latest

There are various solutions throughout the repository. The one you are interested in is located in the \Trunk\Source\Calcium\Xamarin\Installation directory, and is named CalciumTemplates.Xamarin.sln.

The structure of the CalciumTemplates.Xamarin solution is shown in Figure 1. You can see the example 'template' projects have been highlighted. These are the projects that contain much of the example source code that is presented within this article series.

Solution Structure

Figure 1. Structure of CalciumTemplates.Xamarin Solution

Using the AppBar

The AppBar contains a collection of buttons and menu items, and is rendered differently depending on the platform which it is running on. If your app is running on Windows Phone, the AppBar is rendered using the built-in ApplicationBar control. When running on iOS and Android, the AppBar is rendered using the Xamarin Forms toolbar (for the buttons) and dialogs for the menu items.

The AppBar can be bound to lists of button and menu item command or defined purely in XAML, as demonstrated in Listing 1.

Listing 1. Defining an AppBar in XAML

XML
<calcium:AppBar>
       <calcium:AppBar.Buttons>
            <calcium:AppBarItem Text="Foo" Tap="FooButtonHandleTap"
                IconUri="/Views/MainView/Images/AppBar/Check.png" />
              <calcium:AppBarItem Text="Bah" />
       </calcium:AppBar.Buttons>
       <calcium:AppBar.MenuItems>
              <calcium:AppBarItem Text="MenuItem1" />
       </calcium:AppBar.MenuItems>
</calcium:AppBar>   

When I began putting together the code for this article, I decided to go beyond what I had already created in Calcium for Windows Phone. I've constructed the AppBar in a manner that allows developers to better separate the UI from its viewmodel. The AppBar can be bound to a collection of button commands and a collection of menu item commands. I chose to allow binding to a collection of commands, rather than a bespoke model class, because the commanding API is well understood and Calcium's UICommand objects have all that is needed to represent a button or menu item.

In WPF, RoutedUICommands can be used to encapsulate the textual display property of the command. The advantage of this is that the text of a button, for example, can be updated from within the command logic. When Silverlight arrived, the notion of placing UI specific properties within an ICommand implementation was deemed unfavorable. Indeed, RoutedUICommand was not included in the FCL. It was left solely to the UI to display text according to its own state or of its viewmodel. This, I believe, has led to a tendency for business logic to leak into the view layer. The idea of a command may be orthogonal to notions of text and icons, but a command backing a button control, with text or an icon, is so common place that you can’t deny it is a good candidate to be combined into a single type. This, I suppose, is where perceived best practice and pragmatism collide.

When evaluating these kinds of design decisisions, in my view, if the cost is negligible, yet a payoff exists, then you should err on the side of pragmatism, and make API decisions that favour ease of development rather than perceived value that can result in internal complexity. 

Let's move on to an overview of the binding capabilities of the AppBar. The MainViewModel in the downloadable sample code contains two ObservableCollections containing objects of type IUICommand. IUICommands extend the familiar ICommand interface to include a property for text, for visibility, and an icon URL. The properties of an object implementing IUICommand are able to be mapped to a buttom or a menu item in the AppBar. In the following excerpt, an IUICommand allows the user to tap to navigate to the HubView page:

C#
navigateToHubCommand = new UICommand(NavigateToHub)
       {
              Text = AppResources.MainView_AppBar_Buttons_Hub
       };

appBarButtonCommands.Add(navigateToHubCommand);

The NavigateToHub method resolves the HubView page, which is built-up by the IoC container, and the INavigation instance navigates to the HubView, as shown:

C#
async void NavigateToHub(object arg)
{       
    var hubView = Dependency.Resolve<HubView>();
    Navigation.PushAsync(hubView);
}

A menu item is placed in the AppBar by creating another UICommand object. This time, Calcium’s Dialog Service presents the user with a message prompt. The DialogService is responsible for presenting dialogs on Windows Phone, iOS, and Android.

C#
var menuCommand = new UICommand(_ => DialogService.ShowMessageAsync("Menu Item 1 tapped."))
       {
              Text = "Menu Item 1"
       };

appBarMenuCommands.Add(menuCommand);

An XML namespace declaration is included on the MainView page, like so:

C#
xmlns:calcium="clr-namespace:Outcoder.UI.Xaml;assembly=Outcoder.Calcium"

And finally, an AppBar is defined on the MainView page, which is data-bound to the two ObservableCollections, as shown:

XML
<calcium:AppBar
       ButtonCommands="{Binding AppBarButtonCommands}"
       MenuCommands="{Binding AppBarMenuCommands}" />

This is then materialized on all three views as shown in Figure 2. Notice the location of the rendered AppBar buttons across the three platforms.

AppBar Expanded

Figure 2. An AppBar displayed on Windows Phone, iOS, and Android.

Building a Xamarin Forms Custom AppBar

You can create a custom Xamarin Forms control or view, as they are known, by subclassing the Xamarin.Forms.View class.

The Calcium AppBar for Xamarin Forms is composed of an ObservableCollection of buttons and an ObservableCollection of menu items. Buttons and menu items are both represented by an AppBarItem class that implements the IAppBarItem interface. IAppBarItem has the following members:

  • string Text
  • Uri IconUri
  • bool IsEnabled
  • void PerformTap()

The base implementation of the IAppBarItem interface is the AppBarItemBase abstract class. See Figure 3. The AppBarItem class adds commanding support. 

AppBar Class Diagram

Figure 3. The AppBar contains collections of IAppBarItem objects.

The AppBarItemBase class contains two bindable properties that are initialized in the class’s static constructor, like so:

C#
static AppBarItemBase()
{
       TextProperty = BindableProperty.Create(
           "Text", typeof(string), typeof(AppBarItemBase), string.Empty, BindingMode.TwoWay);
       IconUriProperty = BindableProperty.Create(
           "IconUri", typeof(Uri), typeof(AppBarItemBase), null, BindingMode.TwoWay);
}

Notice that the IconUri is of type Uri and not string. The use of strings rather than specialized types for properties is prevalent within the Xamarin XAML VisualElements. Xamarin Forms does not have support for implicit type conversion yet. To allow the IconUri property to be set to a string value in XAML, it is necessary to decorate the property with a TypeConverter attribute, as demonstrated in the following excerpt:

C#
[TypeConverter(typeof(UriTypeConverter))]
public Uri IconUri
{
       get
       {
              return (Uri)GetValue(IconUriProperty);
       }
       set
       {
              SetValue(IconUriProperty, value);
       }
}

A Tap event can be programmatically initiated using the AppBarItemBase class’s PerformTap method. This allows you to pass through a tap event from the native implementations, as you see in the following sections.

The AppBarItem class extends the AppBarItemBase class to add commanding support. Two further bindable properties are included in the AppBarItem class: a Command property and a CommandParameter property.

Commanding is optional with the AppBarItem object. If you so choose, you can rely solely on the Tap event to detect user actions.

When an AppBarItem's Command property is set, my intention was to automatically bind the AppBarItem to the various properties of the command. Unfortunately I ran into a road block here. At the time of writing, Xamarin Forms Binding objects do not have a RelativeSource property, which prevents binding to the Text and IconUri properties of the command. See Listing 2.  The method body is commented out in the repository.

Listing 2. First attempt at AppBarItem.HandleCommandChanged Method

C#
static void BindToCommand(AppBarItem item, ICommand command)
{
    if (command != null)
    {
        var newUICommand = command as IUICommand;
        if (newUICommand != null)
        {
            /* Without a relative source binding capability we can't bind to the command. */
            {
                if (string.IsNullOrEmpty(item.Text))
                {
                    item.SetBinding(TextProperty, new Binding("Command.Text", BindingMode.OneWay));
                }

                if (!string.IsNullOrEmpty(newUICommand.IconUrl))
                {
                    item.SetBinding(IconUriProperty, "Command.IconUrl", BindingMode.OneWay, 
                           new StringToUriConverter());
                }

                item.SetBinding(IsEnabledProperty, new Binding("Command.Enabled", BindingMode.OneWay));
            }
        }
    }
}

As a result, binding to the command's Text property, for example, is done within the binding expression; as shown in Listing 3.

Listing 3. AboutView.xaml AppBar excerpt

XML
<calcium:AppBar Grid.Row="1">

    <calcium:AppBar.Buttons>
        <calcium:AppBarItem Command="{Binding ExampleCommand}" 
                            Text="{Binding ExampleCommand.Text}" />
    </calcium:AppBar.Buttons>
</calcium:AppBar>

When a user taps the native representation of an AppBarItem, the event 'flows through' via the AppBarItem's overidable PerformTap method. If a command has been assigned to the AppBarItem, then the command’s Execute method is called, as shown in the following excerpt:

C#
protected override void OnTap(EventArgs e)
{
       base.OnTap(e);

       var command = Command;
       if (command != null)
       {
              command.Execute(CommandParameter);
       }
}

NOTE. The presence of a command does not override the Tap event. Any subscribers are still notified of the occurrence of a tap before the command is performed.

Adapting Commands to AppBar Items

To recap, you can bind the AppBar’s Buttons and MenuItems properties to IUICommand collections. Clearly there is an intermediary step to resolve what type of IAppBarItem to use to represent a particular IUICommand. Enter the IAppBarItemFactory. An IAppBarItemFactory object’s job is to map commands to the app bar counterparts. IAppBarItemFactory has a single method that accepts an IUICommand and returns an IAppBarItem. The default implementation does not do a whole lot yet, but merely creates an AppBarItem and assigns it the specified command, as shown:

C#
public virtual IAppBarItem BuildItem(IUICommand command)
{
       ArgumentValidator.AssertNotNull(command, "command");
       var result = new AppBarItem {Command = command};
       return result;
}

IAppBarItemFactory is an extensibility point because you can replace the IAppBarItemFactory implementation to perform whatever custom mapping you’d like. The AppBar class’s AdaptItems method retrieves the IAppBarItemFactory from the IoC container and falls back to the default implementation, AppBarItemFactory, if none has been registered. See Listing 4.

Listing 4. AppBar.AdaptItems method

C#
List<IAppBarItem> AdaptItems(IEnumerable<IUICommand> commands)
{
       IAppBarItemFactory factory = Dependency.Resolve<IAppBarItemFactory, AppBarItemFactory>();

       List<IAppBarItem> result = new List<IAppBarItem>();

       foreach (var command in commands)
       {
              IAppBarItem item = factory.BuildItem(command);
              result.Add(item);
       }

       return result;
}

In this section you how to create a custom visual element. The manner in which the AppBar is rendered has to depend on what platform your app is running. In the next section you see how to create platform specific renderers.

Employing Platform Specific Rendering, aka Native Rendering

Let’s get into the nitty gritty of how the AppBar is materialized on multiple platforms without the need for custom plumbing code on your part. The task of carrying out platform specific rendering is performed using the Xamarin Forms rendering APIs. In the case of Calcium’s AppBar, it uses three different renderers: one for iOS, Android, and Windows Phone. In this section you see how each of the renderers is implemented, and you learn how to register a native renderer with the Xamarin Forms rendering system.

In Xamarin Forms, consuming a platform specific control is achieved with a custom ViewRenderer<TElement, TNativeElement> implementation, where TElement is the type of the Xamarin Forms custom view and TNativeElement is the type of the platform specific element, such as a Windows Phone custom control.

NOTE: Though the naming of the generic arguments of the ViewRenderer imply that a VisualElement is sufficient, the type constraints of the ViewRenderer require a class derived from Xamarin.Forms.View.

Displaying a Windows Phone Application Bar using a View Renderer

The built-in Windows Phone application bar is one of the key components on Windows Phone. It’s used by most XAML based apps, and gives uniformity to a key aspect of the user experience. It does, however, have some challengers associated with it, if you desire any type of non-standard behaviour. For example, the Windows Phone 8 ApplicationBar does not support data binding. Unlike the rest of the rich controls available out-of-the-box in Windows Phone Silverlight apps, it is clunky to work with and serves more or less as a shim for the underlying native control. One particular limitation is that it doesn’t support creating multiple application bars for Pivot or Panorama items.

Some time ago I decided to create a wrapper for the built-in application bar, which I have used in a number of commercial apps. It has proved to be a more than useful addition to my toolbox. I have leveraged the features of this custom application bar in Xamarin Forms to provide the same multi-bar support for Xamarin Pages that are placed in a MultiPage container such as a TabbedPage.

The custom Windows Phone AppBar class is rendered by the Windows Phone specific AppBarRenderer class.  You can find the AppBarRenderer class for Windows Phone in the in the Outcoder.Calcium.XamarinForms.WindowsPhone project of the Calcium source repository. The class exists in the Outcoder.UI.Xaml.Renderers namespace.

C#
public class AppBarRenderer : ViewRenderer<Outcoder.UI.Xaml.AppBar, Outcoder.UI.Xaml.Controls.AppBar> 
{ ... }

When creating a custom renderer there are two main things you need to do. The first is that you should react to the ElementChanged event by overriding the OnElementChanged method of the ViewRenderer<TView,TNative> class. When the OnElementChanged method is called it provides the opportunity to initialize the control. The second is that once the native control has been initialized it is supplied to the renderer via the render's SetNativeControl method.

In Listing 5 you see that the method takes care of detaching the old AppBar (unsubscribing to its events) and attaching the new AppBar instance. In addition, the current Xamarin Page instance is located using the custom GetHostPage method. The renderer subscribes to the page’s Disappearing and Appearing events to know when to hide and rebuild the AppBar respectively. The SetNativeControl method gives the Xamarin Forms infrastructure the element to display within the platform specific interface.

Listing 5. Overriding the OnElementChanged method for the Windows Phone AppBar

C#
protected override void OnElementChanged(ElementChangedEventArgs<AppBar> e)
{
       base.OnElementChanged(e);

       var oldElement = e.OldElement;

       if (oldElement != null)
       {
              DetachAppBar(oldElement);
       }

       if (!initialized)
       {
              initialized = true;

              Page page = GetHostPage();
              page.Disappearing += HandlePageDisappearing;
              page.Appearing += HandlePageAppearing;

              var wpAppBar = new Outcoder.UI.Xaml.Controls.AppBar();

              SetNativeControl(wpAppBar);
       }

       AppBar newAppBar = e.NewElement;
       AttachAppBar(newAppBar);
}

The AppBarRenderer keeps a weak reference to the current Page to detect when another page takes ownership of the application bar. The following excerpt shows the HandlePageAppearing method, which creates a new WeakReference object:

C#
void HandlePageAppearing(object sender, EventArgs e)
{
       Page ownerPage = GetHostPage();
       appBarOwner = new WeakReference<Page>(ownerPage);
       UpdateAppBar();
}

Conversely, when the user navigates away from a page, the HandlePageDisappearing method is called. If the new page does not correspond to the page that was registered when the page appeared, then it means that a different page now owns the application bar. The reason for tracking the active page is that the Appearing event of a new page is raised prior to the Disappearing event of the current page.

C#
void HandlePageDisappearing(object sender, EventArgs e)
{
       Page ownerPage;

       if (appBarOwner != null && appBarOwner.TryGetTarget(out ownerPage))
       {
              var hostContentPage = GetHostPage();
              if (ownerPage != hostContentPage)
              {
                     return;
              }
       }

       HideAppBar();
}

Retrieving the host page is achieved by taking a stroll up through the visual tree; calling the custom method GetParentOfType<Page>(). See Listing 6. I intend on incorporating this visual tree logic into the Calcium base library, akin to the visual tree helper logic you see in Calcium’s WPF and Windows Phone specific core.

Listing 6. AppBarRenderer.GetParentOfType

C#
T GetParentOfType<T>() where T : class
{
       T page = null;
       Element parent = Element;

       while (page == null)
       {
              parent = parent.Parent;

              if (parent == null)
              {
                     break;
              }

              page = parent as T;
       }

       return page;
}

When a new AppBar is presented to the renderer, the AttachAppBar method subscribes to the AppBar’s ButtonCollectionChanged and MenuItemCollectionChanged events. See Listing 7.

Listing 7. Windows Phone AppBarRenderer.AttachAppBar Method

C#
void AttachAppBar(AppBar appBar)
{
       if (appBar == null)
       {
              return;
       }

       DetachAppBar(appBar);

       appBar.ButtonCollectionChanged += HandleButtonsChanged;
       appBar.MenuItemCollectionChanged += HandleMenuItemsChanged;
}

When a button or menu item is added or removed, the AppBar is rebuilt via a call to the renderer’s UpdateAppBar method. See Listing 8. Use the Control property of the base ViewRenderer to retrieve the native control that you supplied earlier.

If the AppBar for Xamarin Forms contains menu items, but no buttons, then the Windows Phone application bar is set to a minimized state; reducing its size while still allowing the menu to be expanded.

A new item for the native AppBar is created for each item in the AppBar for Xamarin Forms. The AppBarRenderer assigns a handler to the Click event of each native item, which passes on the event to the PerformTap method of the IAppBarItem.

You may notice that more work needs to be done to respond to changes in the AppBarItem objects. I have plans to make property changes from the Xamarin Forms IAppBarItem objects flow through to the native AppBar items.

Listing 8. Windows Phone AppBarRenderer.UpdateAppBar Method

C#
void UpdateAppBar()
{
       var wpAppBar = Control;

       if (wpAppBar == null)
       {
              return;
       }

       wpAppBar.Buttons.Clear();
       wpAppBar.MenuItems.Clear();

       var xamAppBar = Element;
       var xamButtons = xamAppBar.Buttons.ToList();
       var xamMenuItems = xamAppBar.MenuItems.ToList();

       if (!xamButtons.Any())
       {
              if (!xamMenuItems.Any())
              {
                     wpAppBar.IsVisible = false;
                     return;
              }

              wpAppBar.Mode = Microsoft.Phone.Shell.ApplicationBarMode.Minimized;
       }

       wpAppBar.IsVisible = true;

       foreach (var item in xamAppBar.Buttons)
       {
              var button = new Outcoder.UI.Xaml.Controls.AppBarIconButton();
              button.Text = item.Text;
              button.IconUri = item.IconUri;
              IAppBarItem i = item;
              button.Click += (sender, args) => i.PerformTap();
              wpAppBar.Buttons.Add(button);
       }

       foreach (IAppBarItem item in xamAppBar.MenuItems)
       {
              var menuItem = new Outcoder.UI.Xaml.Controls.AppBarMenuItem();
              menuItem.Text = item.Text;
              /* Menu item icons are not supported on Windows Phone. */
              //menuItem.IconUri = item.IconUri;
              IAppBarItem i = item;
              menuItem.Click += (sender, args) => i.PerformTap();
              wpAppBar.MenuItems.Add(menuItem);
       }
}

Using a View Renderer to Display a Toolbar in iOS

As an aside, when I began working on building a cross-platform AppBar, the Xamarin APIs were rather different. The developers at Xamarin did some refactoring to unify the rendering system across the three platforms; not a bad thing.

My initial intention was to leverage the platform specific APIs for populating a toolbar on iOS and Android. It turned out, however, that my code and Xamarin’s began to step on each other’s toes. You see, the way you create a toolbar in Xamarin Forms is by populating the ToolbarItems property of a Xamarin Forms Page. The Page renderer then constructs the toolbar when it is building up the page. This interfered with my AppBar. I changed tack and rather than build-up toolbars manually, I instead decided to leverage the Xamarin Forms ToolbarItems property; populating it according to the content of the AppBar.

You’ll notice that in Listing 9, the iOS AppBarRenderer sets its native control to a label. This is because there’s no native backing control that I needed to manipulate, but I still needed to supply something to the Xamarin rendering subsystem. I’m hoping the Xamarin crew will advise me of an alternative to providing a fake control.

There are differences in how images are resolved on each platform, making it difficult to provide a unified approach for placing and locating images. I believe I’ve come up with some neat ways around this. You see a whole section devoted to this in a later article in this series. The IImageUrlTransformer object is related to this aspect, so please disregard it for now, as you will return to it later.

The triggers for populating the toolbar are nearly identical to the Windows Phone implementation; the AppRenderer relies on the active Page’s Appearing and Disappearing events to determine when it should populate the Xamarin Forms ToolbarItems property of the current page.

Listing 9. iOS AppBarRenderer.OnElementChanged Method

C#
protected override void OnElementChanged(ElementChangedEventArgs<AppBar> e)
{
       base.OnElementChanged(e);

       var newAppBar = e.NewElement;

       if (!initialized)
       {
              initialized = true;

              imageUrlTransformer = Dependency.Resolve<IImageUrlTransformer, ImageUrlTransformer>(true);

              /* An Exception is raised if a control is not provided. */
              SetNativeControl(new UILabel(RectangleF.Empty));

              Page page = GetHostContentPage();
              page.Disappearing += (sender, args) =>
              {
                     DetachAppBar(newAppBar);
                     Page ownerPage;

                     if (toolbarOwner != null && toolbarOwner.TryGetTarget(out ownerPage))
                     {
                           var hostContentPage = GetHostContentPage();

                           if (ownerPage != hostContentPage)
                           {
                                  return;
                           }
                     }

                     RemoveToolbar();
              };

              page.Appearing += (sender, args) =>
              {
                     Page ownerPage = GetHostContentPage();
                     toolbarOwner = new WeakReference<Page>(ownerPage);
                     UpdateAppBar();
                     AttachAppBar(newAppBar);
              };
       }
}

You now look at how the AppBar is represented using the Xamarin Forms ToolbarItems collection. A ToolbarItem is created for each button in the AppBar. See Listing 10. When the Activated event of the ToolBarItem is raised, the event handler calls the PerformTap method of the AppBarItem.

A menu item collection does not exist for the toolbar. The AppBarRenderer therefore constructs a special menu button, which is analogous to the ellipsis button on the Windows Phone application bar. When the menu button is activated, the custom DisplayMenu method is called. We turn to that, next.

Listing 10. iOS AppRenderer. UpdateAppBar Method

C#
void UpdateAppBar()
{
    var appBar = Element;
    Page page = GetParentOfType<ContentPage>();

    if (page == null)
    {
        return;
    }

    List<ToolbarItem> items = null;

    foreach (IAppBarItem appBarItem in appBar.Buttons)
    {
        string text;

        if (!AppBarItemPropertyResolver.TryGetItemText(appBarItem, out text))
        {
            throw new Exception("Unable to resolve text for button.");
        }

        ToolbarItem item = new ToolbarItem { Name = text };

        IAppBarItem itemForClosure = appBarItem;
        item.Activated += (sender, e) => itemForClosure.PerformTap();

        string iconUrl;

        if (AppBarItemPropertyResolver.TryGetItemUrl(appBarItem, out iconUrl))
        {
            string transformedUrl = imageUrlTransformer.TransformForCurrentPlatform(iconUrl);
            item.Icon = transformedUrl;
        }

        if (items == null)
        {
            items = new List<ToolbarItem>();
        }

        items.Add(item);
    }

    var menuItemsEnumerable = appBar.MenuItems;
    if (menuItemsEnumerable != null)
    {
        var menuItems = menuItemsEnumerable.ToList();
        int menuItemCount = menuItems.Count;
        if (menuItemCount > 0)
        {
            string[] buttonTitles = new string[menuItemCount];
            for (int i = 0; i < menuItemCount; i++)
            {
                string text;

                if (!AppBarItemPropertyResolver.TryGetItemText(menuItems[i], out text))
                {
                    throw new Exception("Unable to resolve text for menu item.");
                }

                buttonTitles[i] = text;
            }

            ToolbarItem item = new ToolbarItem { Name = menuToolbarItemText };
            item.Activated += async (sender, e) =>
            {
                var viewController = UIApplication.SharedApplication.Windows[0].RootViewController;
                var uiView = viewController.View;

                var action = await DisplayMenu(uiView, "Cancel", buttonTitles);
                if (action > -1)
                {
                    var selectedItem = menuItems[action];
                    selectedItem.PerformTap();
                }
            };

            if (items == null)
            {
                items = new List<ToolbarItem>();
            }

            items.Add(item);
        }
    }

    if (items != null && items.Any())
    {
        var toolbarItems = page.ToolbarItems;
        toolbarItems.Clear();
        items.Reverse();
        toolbarItems.AddRange(items);
    }
}

The AppBarItemPropertyResolver class is used to retrieve the text and icon URL for the AppBarItem. It does so by first attempting to retrieve the property directly from the AppBarItem. If null, then the property is retrieve from the AppBarItem's command. See Listing 11.

Listing 11. AppBarItemPropertyResolver.TryGetItemText method.

C#
internal static bool TryGetItemText(IAppBarItem appBarItem, out string text)
{
    bool resolvedText = false;

    text = appBarItem.Text;

    if (!string.IsNullOrEmpty(text))
    {
        resolvedText = true;
    }
    else
    {
        var commandItem = appBarItem as AppBarItem;
        if (commandItem != null && commandItem.Command != null)
        {
            var uiCommand = commandItem.Command as IUICommand;
            if (uiCommand != null)
            {
                text = uiCommand.Text;
                resolvedText = true;
            }
        }
    }

    return resolvedText;
}

Displaying a Custom Menu in Xamarin.iOS

The iOS AppBarRenderer relies on the UIActionSheet class to display a list of options. Buttons are added to the UIActionSheet object using its AddButton method. See Listing 12.

The DisplayMenu method is awaitable, and the UI thread is not blocked while the UIActionSheet is being displayed. A TaskCompletionSource<int> is used to await closure of the UIActionSheet and the result int value is the index of the button that is tapped, or -1 if it is cancelled.

Listing 12. iOS AppBarRenderer.DisplayMenu Method

C#
Task<int> DisplayMenu(UIView uiView, string cancelText, params string[] buttons)
{
       var sheet = new UIActionSheet();

       foreach (var button in buttons)
       {
              sheet.AddButton(button);
       }

       int cancelButtonIndex = sheet.ButtonCount;
       sheet.AddButton(cancelText);
       sheet.CancelButtonIndex = cancelButtonIndex;

       TaskCompletionSource<int> source = new TaskCompletionSource<int>();

       try
       {
              sheet.Clicked += delegate(object sender, UIButtonEventArgs args)
              {
                     int result;

                     if (args != null)
                     {
                           int buttonIndex = args.ButtonIndex;

                           if (buttonIndex != cancelButtonIndex)
                           {
                                  result = buttonIndex;
                           }
                           else
                           {
                                  result = -1;
                           }
                     }
                     else
                     {
                           result = -1;
                     }

                     source.SetResult(result);
              };

              sheet.ShowInView(uiView);
       }
       catch (Exception ex)
       {
              source.SetException(ex);
       }

       return source.Task;
}

The UIActionSheet is displayed when the user taps the ellipsis button in our custom AppBar. See Figure 4. The Tap event of each AppBarItem is raised and if there is a command associated with the AppBarItem it is performed.

iOS AppBar Expanded

Figure 4. Displaying a Menu using the UIActionSheet.

 

Using a View Renderer to Display an Android Toolbar

In this final part of the overview of the AppBar view renderers, you look at the AppBar ViewRenderer implementation for Android.

The AppBarRenderer for Android is located in the Outcoder.Calcium.Android project in the Calcium source code repository. It closely resembles the iOS AppBarRenderer in that it also leverages the Xamarin Forms ToolbarItems property of the active page. So, there’s very little native activity that takes place. The key difference is in the way the menu is displayed to the user. See Listing 13.

The Calcium ActionDialog is used to present a list of options to the user. The DisplayMenu method is awaitable, and returns a Task<int>. The result of the method is the index of the button that was tapped.

Listing 13. Android AppBarRenderer.DisplayMenu Method

C#
Task<int> DisplayMenu(params string[] buttons)
{
       ActionDialogArguments arguments = new ActionDialogArguments(null, null, null, buttons);
       ActionDialog dialog = new ActionDialog(arguments, Context);

       dialog.Show();

       return arguments.Result.Task;
}

ActionDialog resembles the Xamarin.Android ActionSheet class. There are, however, a couple notable difference. Calcium's ActionDialog uses an index based system rather than a string based system to identify which button was tapped. It also allows the title of the dialog to be hidden. Something that I thought made sense with the AppBar scenario. It does this by calling the base Dialog class’s RequestWindowFeature, as shown in the following excerpt from the ActionDialog’s OnCreate method:

C#
protected override void OnCreate(Bundle savedInstanceState)
{
       string actionSheetTitle = arguments.ActionSheetTitle;

       bool showTitle = !string.IsNullOrWhiteSpace(actionSheetTitle);

       if (!showTitle)
       {
              RequestWindowFeature((int)WindowFeatures.NoTitle);
       }

       base.OnCreate(savedInstanceState);

…

}

NOTE. In the event that you create your own custom Android Dialog, the RequestWindowFeature call needs to be made before the base.OnCreate method call.

I’ll not detail the rest of the ActionDialog code here. If you are interested in that, you can find the ActionDialog class in the Outcoder.Calcium.Android project in the Calcium source code repository.

Tapping the ellipsis button on Android presents the menu items. See Figure 5.

Android with Menu Expanded

Figure 5. AppBar menu in Android

Registering a View Renderer

To register a Xamarin Forms native renderer you use an ExportRenderer attribute, like so:

C#
[assembly: ExportRenderer(typeof(Outcoder.UI.Xaml.AppBar),
    typeof(Outcoder.UI.Xaml.Renderers.AppBarRenderer))]

At the time of writing there is a platform disparity when exporting renderers. Xamarin.Android and Xamarin.iOS projects both allow you to export a renderer from within a class library. This is not the case with Windows Phone. To use the AppBar in a Windows Phone project you must explicitly add an ExportRenderer attribute to your code. In the sample, I chose to do this within the MainPage.xaml.cs file in the Windows Phone project.

Conclusion

In this article you looked at defining a custom AppBar in XAML; binding it to collections of command objects and at directly binding AppBar items to viewmodel properties. You saw how to employ platform specific rendering to display an AppBar on iOS, Android, and Windows Phone. You learned how the Calcium AppBar is multi-page aware and can be placed within multiple pages hosted in a CarouselPage or a TabbedPage. You saw how to display a custom menu in iOS and Android. Finally, you looked at how to register a custom view renderer.

In the next article you learn how to place images anywhere in a shared project as Content resources like in Windows Phone, and consume them in the same way across all three platforms.

I hope you find this project useful. If so, then I'd appreciate it if you would rate it and/or leave feedback below. This will help me to make my next article better.

History

October 2014

  • Initial publication.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
Switzerland Switzerland
Daniel is a former senior engineer in Technology and Research at the Office of the CTO at Microsoft, working on next generation systems.

Previously Daniel was a nine-time Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed mobile apps including Surfy Browser for Android and Windows Phone. Daniel is the creator of a number of popular open-source projects, most notably Codon.

Would you like Daniel to bring value to your organisation? Please contact

Blog | Twitter


Xamarin Experts
Windows 10 Experts

Comments and Discussions

 
PraiseExcellent! 5 Votes Pin
VR Karthikeyan22-Nov-16 19:28
professionalVR Karthikeyan22-Nov-16 19:28 
GeneralRe: Excellent! 5 Votes Pin
Daniel Vaughan23-Nov-16 0:59
Daniel Vaughan23-Nov-16 0:59 
GeneralExcellent Pin
Anil Sharma198322-Nov-15 20:31
professionalAnil Sharma198322-Nov-15 20:31 
GeneralRe: Excellent Pin
Daniel Vaughan23-Nov-15 0:50
Daniel Vaughan23-Nov-15 0:50 
Question5! Pin
Krunal Rohit16-Nov-15 19:52
professionalKrunal Rohit16-Nov-15 19:52 
AnswerRe: 5! Pin
Daniel Vaughan16-Nov-15 23:12
Daniel Vaughan16-Nov-15 23:12 
GeneralMy vote of 5 Pin
D V L8-Sep-15 21:24
professionalD V L8-Sep-15 21:24 
GeneralRe: My vote of 5 Pin
Daniel Vaughan8-Sep-15 23:23
Daniel Vaughan8-Sep-15 23:23 
QuestionNice! Pin
Volynsky Alex7-Oct-14 11:58
professionalVolynsky Alex7-Oct-14 11:58 
AnswerRe: Nice! Pin
Daniel Vaughan7-Oct-14 12:35
Daniel Vaughan7-Oct-14 12:35 
GeneralRe: Nice! Pin
Volynsky Alex8-Oct-14 5:31
professionalVolynsky Alex8-Oct-14 5:31 
GeneralMy vote of 5 Pin
Pete O'Hanlon7-Oct-14 11:27
subeditorPete O'Hanlon7-Oct-14 11:27 
GeneralRe: My vote of 5 Pin
Daniel Vaughan7-Oct-14 12:34
Daniel Vaughan7-Oct-14 12:34 
Questionnice Pin
Sacha Barber7-Oct-14 9:38
Sacha Barber7-Oct-14 9:38 
AnswerRe: nice Pin
Daniel Vaughan7-Oct-14 10:31
Daniel Vaughan7-Oct-14 10:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.