Xamarin.Forms: Creating a sliding drawer with behaviors, messages and MVVM Light

Sliding Drawers have become a popular, almost required form of navigation in phone applications.  There are libraries that will do this for you, but you can use Xamarin.Forms to create what you want without too much effort.  Okay, with quite a bit of effort, but it is like following a recipe.  Ok, a complicated recipe.  So fasten your seatbelts…

How the Pieces Fit Together Sliding Drawers

We’re going to have two kinds of pages:

  • Normal content pages
  • Master and Detail pages

We begin, as usual by creating our MVVM folders, and one more for Behaviors.  Let’s create four pages that we’ll navigate among.  We can just name them Page 1, Page 2, Page 3 and Page 4, though in a real application you’ll no doubt give them more meaningful names.

Now it’s time to create the navigation, based on your Master/Details pages.  For this we need to declare two pages:

  • Our Root page: type MasterDetailPage
  • Our Drawer page: type Content Page

These two work together to have the drawer page slide over the current page and offer navigation as shown above.

Root Page

The RootPage has virtually no XAML,

<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SlidingDrawer.View.RootPage"
                  Title="Sliding Drawer">
</MasterDetailPage>

Notice the title– that is required.

The fun is in the code behind (yes, I know, we really don’t like putting code in code-behind),

We start with the constructor,

    public RootPage( )
    {
      InitializeComponent( );
      var drawerPage = new Drawer( );
      Messenger.Default.Register( 
                 this, ( m ) => NavigateToNewPage( m ) );
      Master = drawerPage;
      Detail = new NavigationPage( new Page1( ) );

    }

There are a few things to notice here.  First, the call to Messenger.  This draws on MVVM Light, which you easily obtain form NuGet.

The second is that we’re setting the built-in Master property to the drawer page and the built-in Detail property to page 1.  This gets us started.  In App.cs we set the MainPage to this RootPage,

    public App( )
    {
      InitializeComponent( );

      MainPage = new RootPage( );
    }

That sets up our basic navigation pattern.

The Drawer

Let’s turn to the DrawerPage.  Here is the XAML for the drawer.  It is based on a ListView,

<?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:behaviors="clr-namespace:
        SlidingDrawer.Behaviors;assembly=SlidingDrawer"
             Title="Sliding Drawer"
             x:Class="SlidingDrawer.View.Drawer">
  <StackLayout BackgroundColor="#EFEFEF">
    <ContentView Padding="20,20,0,0" BackgroundColor="#2196F3">
      <StackLayout>
        <StackLayout Orientation="Horizontal">
          <Label Text="Liberty" TextColor="#FFFFFF" 
                  FontSize="32"/>
        </StackLayout>
      </StackLayout>
    </ContentView>
    <ListView x:Name="ListViewMenu" 
             ItemsSource="{Binding MenuItems}" >
      <ListView.Behaviors>
        <behaviors:EventToCommandBehavior 
            EventName="ItemSelected" 
            Command="{Binding MenuItemSelectedCommand}" />
      </ListView.Behaviors>
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <Grid>
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
              </Grid.ColumnDefinitions>
              <Grid.RowDefinitions>
                <RowDefinition Height="40" />
              </Grid.RowDefinitions>
              <Label Text="{Binding Title}" Grid.Row="0" 
                 FontSize="18" 
                 TextColor="#555555" 
                 Grid.Column="1" 
                 VerticalTextAlignment="Center" />
            </Grid>
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </StackLayout>
</ContentPage>

That’s a fair amount of XAML but the only tricky part is the ListView’s Behaviors section. This is where we create our first behavior,  Our goal is to turn the event ItemSelected into a command we can handle in the code behind.

Event To Command Behavior

Much has already been written about EventsToCommands, so I’ll just skim over the code.  We begin by creating the base class,

 public class BehaviorBase : Behavior where T : BindableObject
  {
    public T AssociatedObject { get; private set; }

This derives from the built-in Behavior class and is, as you can see, generic.

Thee are four key methods. First is the method called when the behavior is attached to the target element (the AssociatedObject)

  protected override void OnAttachedTo( T bindable )
    {
      base.OnAttachedTo( bindable );
      AssociatedObject = bindable;

      if (bindable.BindingContext != null)
      {
        BindingContext = bindable.BindingContext;
      }

      bindable.BindingContextChanged += OnBindingContextChanged;
    }

Next, we handle OnDetachingFrom, an often overlooked method which helps prevent memory leaks,

   protected override void OnDetachingFrom( T bindable )
    {
      base.OnDetachingFrom( bindable );
      bindable.BindingContextChanged -= OnBindingContextChanged;
      AssociatedObject = null;
    }

Finally, we handle the OnBindingContextChanged event by calling hte overridden OnBindingContextchanged

    void OnBindingContextChanged( object sender, EventArgs e )
    {
      OnBindingContextChanged( );
    }

    protected override void OnBindingContextChanged( )
    {
      base.OnBindingContextChanged( );
      BindingContext = AssociatedObject.BindingContext;
    }

The EventToCommandBehavor

We now turn to the derived class, which of course derives from the base class we just created,

  public class EventToCommandBehavior : BehaviorBase
  {
    Delegate eventHandler;

Let’s start with the four properties,

    public static readonly BindableProperty EventNameProperty = 
      BindableProperty.Create( "EventName", typeof( string ), 
        typeof( EventToCommandBehavior ), null, 
        propertyChanged: OnEventNameChanged );

    public static readonly BindableProperty CommandProperty = 
      BindableProperty.Create( "Command", typeof( ICommand ), 
        typeof( EventToCommandBehavior ), null );

    public static readonly BindableProperty 
               CommandParameterProperty = 
      BindableProperty.Create( "CommandParameter", 
      typeof( object ),
      typeof( EventToCommandBehavior ), null );

    public static readonly BindableProperty 
    InputConverterProperty = 
      BindableProperty.Create( "Converter",
        typeof( IValueConverter ),
        typeof( EventToCommandBehavior ), null );

For each of these we need a private pair of GetValue and SetValue commands,

   public string EventName
    {
      get { return (string)GetValue( EventNameProperty ); }
      set { SetValue( EventNameProperty, value ); }
    }

    public ICommand Command
    {
      get { return (ICommand)GetValue( CommandProperty ); }
      set { SetValue( CommandProperty, value ); }
    }

    public object CommandParameter
    {
      get { return GetValue( CommandParameterProperty ); }
      set { SetValue( CommandParameterProperty, value ); }
    }

    public IValueConverter Converter
    {
      get { return (IValueConverter)GetValue( 
                InputConverterProperty ); }
      set { SetValue( InputConverterProperty, value ); }
    }

Next, because these are attached properties, we need the associated methods. Let’s start with OnAttachedTo and OnDetachingFrom,

  
 protected override void OnAttachedTo( 
           Xamarin.Forms.View bindable )
    {
      base.OnAttachedTo( bindable );
      RegisterEvent( EventName );
    }

    protected override void OnDetachingFrom( 
             Xamarin.Forms.View bindable )
    {
      DeregisterEvent( EventName );
      base.OnDetachingFrom( bindable );
    }

Now we need the ability to attach to and detach from an element,

   protected override void OnAttachedTo( 
            Xamarin.Forms.View bindable )
    {
      base.OnAttachedTo( bindable );
      RegisterEvent( EventName );
    }

    protected override void OnDetachingFrom(
              Xamarin.Forms.View bindable )
    {
      DeregisterEvent( EventName );
      base.OnDetachingFrom( bindable );
    }

Ok, now things get interesting. First is the code for when the event is registered,

   void RegisterEvent( string name )
    {
      if (string.IsNullOrWhiteSpace( name ))
      {
        return;
      }

      EventInfo eventInfo = AssociatedObject.GetType( )
             .GetRuntimeEvent( name );
      if (eventInfo == null)
      {
        throw new ArgumentException(  
             "$EventToCommandBehavior: Can't register the 
                  '{EventName}' event."  );
      }
      MethodInfo methodInfo = typeof( EventToCommandBehavior )
            .GetTypeInfo( ).GetDeclaredMethod( "OnEvent" );
      eventHandler = methodInfo.CreateDelegate( 
            eventInfo.EventHandlerType, this );
      eventInfo.AddEventHandler( AssociatedObject, eventHandler );
    }

Each attached oobject is associated with an object to which they are attaching. That is the AssociatedObject described above, and seen earlier in the base class.

Deregistering Events follows (and helps prevent memory leaks),

void DeregisterEvent( string name )
    {
      if (string.IsNullOrWhiteSpace( name ))
      {
        return;
      }

      if (eventHandler == null)
      {
        return;
      }
      EventInfo eventInfo = AssociatedObject.GetType( )
              .GetRuntimeEvent( name );
      if (eventInfo == null)
      {
        throw new ArgumentException( "$EventToCommandBehavior: 
            Can't de-register the '{EventName}' event." );
      }
      eventInfo.RemoveEventHandler( 
           AssociatedObject, eventHandler );
      eventHandler = null;
    }

We now turn to event handling,

    void OnEvent( object sender, object eventArgs )
    {
      if (Command == null)
      {
        return;
      }

      object resolvedParameter;

      if (CommandParameter != null)
      {
        resolvedParameter = CommandParameter;
      }
      else if (Converter != null)
      {
        resolvedParameter = Converter.Convert( 
                 eventArgs, typeof( object ), null, null );
      }
      else if (eventArgs is SelectedItemChangedEventArgs)
      {
        resolvedParameter = 
            ((SelectedItemChangedEventArgs)eventArgs)
              .SelectedItem;
      }
      else
      {
        resolvedParameter = eventArgs;
      }

      if (Command.CanExecute( resolvedParameter ))
      {
        Command.Execute( resolvedParameter );
      }
    }

And finally, we need OnEventNameChanged,

  static void OnEventNameChanged( 
             BindableObject bindable, 
             object oldValue, 
             object newValue )
    {
      var behavior = (EventToCommandBehavior)bindable;
      if (behavior.AssociatedObject == null)
      {
        return;
      }

      string oldEventName = (string)oldValue;
      string newEventName = (string)newValue;

      behavior.DeregisterEvent( oldEventName );
      behavior.RegisterEvent( newEventName );
    }

Finishing up with drawer, we turn to the code behind where we simply create and set the ViewModel, and set it as the BindingContext,

 public partial class Drawer : ContentPage
  {
    private DrawerViewModel vm;
    public Drawer( )
    {
      InitializeComponent( );
      vm = new DrawerViewModel();
      BindingContext = vm;
    }
  }

The View Model – DrawerViewModel

DrawerViewModel inherits from MVVM Light’s ViewModelBase

We want to add MenuItems to our observable collection of MenuItem,  but first we need to declare our MenuItems class.  In the Model folder we declare that simple clasee,

  public class MenuItem
  {
    public string Title { get; set; }
    public Type TargetType { get; set; }
  }

Next, we declare the collection

  public ObservableCollection MenuItems { get; set; } = 
          new ObservableCollection( );

We return to the constructor where we create four MenuItem objects and add them to the collection,

   public DrawerViewModel( )
    {
      MenuItems.Add( new Model.MenuItem( )
      {
        Title = "Page 1",
        TargetType = typeof( Page1 )
      } );
      MenuItems.Add( new Model.MenuItem( )
      {
        Title = "Page 2",
        TargetType = typeof( Page2 )
      } );
      MenuItems.Add( new Model.MenuItem( )
      {
        Title = "Page 3",
        TargetType = typeof( Page3 )
      } );
      MenuItems.Add( new Model.MenuItem( )
      {
        Title = "Page 4",
        TargetType = typeof( Page4 )
      } );

    }

While we are here, let’s handle the menuItemSelectedCommand called in the ListView.  For this we use MVVM Light’s RelayCommand,

    private RelayCommand menuItemSelectedCommand;

    public RelayCommand MenuItemSelectedCommand
    {
      get
      {
        return menuItemSelectedCommand ??
               (menuItemSelectedCommand =
                 new RelayCommand(
                   ( menuItem ) => Messenger.Default.Send( 
                        new DrawerItemMessage( 
                           menuItem.TargetType ) ) ));

      }

Communication

Communication from the View Model back to the page requires the existenace of a DrawerItemMessage.  as seen above.  This is declared in the Model folder,

  public class DrawerItemMessage
  {
    public DrawerItemMessage( Type pageType )
    {
      NavigateTo = pageType;
    }

    public Type NavigateTo { get; set; }

  }

 


This blog post would not have been possible without the valuable assistance of Eric Glover. Eric is not only an expert in MVVM Light, but also one of the brightest and skilled  programmers I’ve had the pleasure to work with.

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Essentials. Bookmark the permalink.

3 Responses to Xamarin.Forms: Creating a sliding drawer with behaviors, messages and MVVM Light

Comments are closed.