Mobile Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Rowi-like "Show-On-Tap" Menu for Windows Phone

04.08.2012
| 4872 views |
  • submit to reddit

As Rowi, a great Twitter client for Windows Phone, pushed its next update, it introduced a very interesting type of menu - "show-on-tap". It is displayed whenever the user taps on a single tweet anywhere in the application. It is basically a list of options that are tied to the ItemTemplate of the ListBox, where the items are aggregated. You can see what it looks like in the screenshots below:

I thought that for one of the applications I am working on, such a concept would work much better than a "push-and-wait" tooltip menu, that developers are accustomed to using right now. The main drawback connected to using the traditional tooltip is the fact that the user doesn't know if that option is available or not - there is a required reaction time for the control to show up. 

A "show-on-tap" menu, on the other hand, provides instant feedback - once the user clicks on the item, the associated actions are instantly displayed. That's how I decided to implement my own control that will do just that - TapMenu. Thinking about it's structure, it is easy to spot several basic components that should be a part of it:

  • ItemsPresenter with a horizontal ScrollViewer - that way items are positioned sequentially in horizontal space.
  • TapMenuButton instances inside the ListBox - these are separate square buttons with a custom DataTemplate to accomodate an Image and a TextBlock for the caption.
  • A semi-transparent Grid - used to contain the ListBox and still provide a visual representation of the content behind it.

I created a new Class Library project for Windows Phone and named it ControlKit. It will ultimately become my own collection of controls that I created, so might as well keep the naming simple yet descriptive. I added a class called TapMenu, and here is what's in it:

using System.Windows.Controls;

namespace ControlKit
{
    public class TapMenu : ItemsControl
    {
        public TapMenu()
        {
            DefaultStyleKey = typeof(TapMenu);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
        }
    }
}

Nothing fancy - it inherits from ItemsControl, so I can easily display a multitude of items inside it. Most of the control declaration is happening in the theme file and I will be talking about that later. The TapMenu will contain multiple instances of TapMenuButton - those need to contain an image and a caption. Here is what the TapMenuButton class looks like:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace ControlKit
{
    public class TapMenuButton : Button
    {
        public static readonly DependencyProperty TextProperty =
          DependencyProperty.Register("Text", typeof(string), typeof(TapMenuButton), new PropertyMetadata(string.Empty));

        public static readonly DependencyProperty ImageSourceProperty =
          DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(TapMenuButton), null);

        public TapMenuButton()
        {
            DefaultStyleKey = typeof(TapMenuButton);
        }

        public ImageSource ImageSource
        {
            get { return GetValue(ImageSourceProperty) as ImageSource; }
            set { SetValue(ImageSourceProperty, value); }
        }

        public string Text
        {
            get { return GetValue(TextProperty) as string; }
            set { SetValue(TextProperty, value); }
        }
    }
}

Now you need to set the control's visual style. This is done in the Generic.xaml file - create a new folder in the project and name it Themes. Then, add a new file and name it Generic.xaml.

NOTE: Naming is important - the current convention is a Silverlight requirement and has to be followed for the controls to be rendered correctly. 

Here is what I have in there:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    xmlns:local="clr-namespace:ControlKit">
    <Style TargetType="local:TapMenu">
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <StackPanel Opacity=".8" Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>

        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:TapMenu">
                    <ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Hidden"  Height="150">
                         <ItemsPresenter></ItemsPresenter>
                    </ScrollViewer>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style TargetType="local:TapMenuButton">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:TapMenuButton">
                    <Button Margin="0,0,-15,0" Height="120" Width="120" Background="{StaticResource PhoneAccentBrush}" BorderThickness="0" Padding="0">
                        <StackPanel>
                            <Image Margin="0,12,0,12" HorizontalAlignment="Center" Height="36" Width="36" Stretch="UniformToFill" Source="{TemplateBinding ImageSource}"></Image>
                            <TextBlock Style="{StaticResource PhoneTextSmallStyle}" HorizontalAlignment="Center" Text="{TemplateBinding Text}"></TextBlock>
                        </StackPanel>
                    </Button>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

There are several things worth mentioning:

  • Notice that the StackPanel that represents the ItemsPanelTemplate has the Orientation property set to Horizontal. This ensures that the elements that will be used in the control will be stacked in the horizontal plane.
  • For the main TapMenu template, notice that the ScrollViewer has the vertical scrolling disabled and the horizontal scrollbar is hidden.
  • For TapMenuButton, both the Image and TextBlock use TemplateBinding. Don't make the mistake and use Binding - the property you need to read is a part of the main template.
  • TapMenuButton is a borderless implementation of the simple Button control.
  • The backgroung color brush for TapMenuButton is determined by the current accent color set by the user.

The control is now ready. In order to test it, create a new Windows Phone Silverlight Application project in the context of the same solution. Add a reference to ControlKit. In MainPage.xaml add a reference to the ControlKit assembly by adding an extra namespace:

xmlns:ckit="clr-namespace:ControlKit;assembly=ControlKit"

Now you can do something like this:

<ckit:TapMenu>
    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
</ckit:TapMenu>

The rendered result will look exactly like this:

Great, but Rowi shows this menu whenever the user taps on one of the items in a ListBox. As you can see from the image above, I conveniently implemented a sample list with completely irrelevant data. The current ListBox XAML is declared like this:

<ListBox ItemsSource="{Binding Path=Instance.RandomTextItems,Source={StaticResource Binder}}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Tap="Grid_Tap">
                <StackPanel Margin="0,0,0,30">
                    <TextBlock Style="{StaticResource PhoneTextGroupHeaderStyle}" Text="{Binding TextOne}"></TextBlock>
                    <TextBlock Style="{StaticResource PhoneTextSmallStyle}" Text="{Binding TextTwo}"></TextBlock>
                    <TextBlock Style="{StaticResource PhoneTextAccentStyle}" Text="{Binding TextThree}"></TextBlock>
                </StackPanel>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The sample code for TapMenu from above can be easily transferred here in the DataTemplate working Grid. Unlike in a StackPanel, the controls in a Grid are positioned on top of each other unless explicitly declared otherwise. That is the perfect scenario for what I am about to do:

<ListBox ItemsSource="{Binding Path=Instance.RandomTextItems,Source={StaticResource Binder}}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Tap="Grid_Tap">
                <StackPanel Margin="0,0,0,30">
                    <TextBlock Style="{StaticResource PhoneTextGroupHeaderStyle}" Text="{Binding TextOne}"></TextBlock>
                    <TextBlock Style="{StaticResource PhoneTextSmallStyle}" Text="{Binding TextTwo}"></TextBlock>
                    <TextBlock Style="{StaticResource PhoneTextAccentStyle}" Text="{Binding TextThree}"></TextBlock>
                </StackPanel>

                <ckit:TapMenu x:Name="tapMenu" Visibility="Collapsed">
                    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
                    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
                    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
                    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
                    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
                    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
                    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
                    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
                    <ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
                </ckit:TapMenu>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The TapMenu now has a name and is by default invisible. The container grid has an event handler that handles the Tap event - Grid_Tap:

private void Grid_Tap(object sender, GestureEventArgs e)
{
    Grid mainGrid = sender as Grid;
    TapMenu menu = mainGrid.FindName("tapMenu") as TapMenu;
    if (menu.Visibility == System.Windows.Visibility.Collapsed)
        menu.Visibility = System.Windows.Visibility.Visible;
    else
        menu.Visibility = System.Windows.Visibility.Collapsed;
}

If the user taps on the container grid and TapMenu is not visible, it will show up. Otherwise it will become collapsed.

You can add additional event handlers for individual TapMenuButton instances, like Click.

The visual output will be this:

You can download the full project source on GitHub.