Introduction

In many Windows Store apps typically there is one or more lists of items that can be filtered and sorted. Windows Store apps UX guidelines suggest us to implement filters with a horizontal list of combo boxes under the page title.

A typical example of this solution is the Windows Store: after searching for an app by name, you can filter by category and price and you can also sort.

This solution is simple and straightforward but has some disadvantages:
  • When the number of filters grows, there is not a lot of space especially at smaller resolutions (1024x768 or 1366x768).
  • For each filter you can select only a single value, there is no multi selection.
With the release of Windows 8.1 we have two new apps preinstalled, Food & Drink and Health & Fitness that introduced a new way of filtering items of a list: there is no more a horizontal list of combo boxes under the page title, there is only a button that shows a flyout with the list of available filters. When you click a filter, you can select one or more values from a flyout placed at the right of the filter.

This solution solves the problems described above since:
  • You can have an unlimited number of filters since the flyout has a vertical scrollbar so you don’t have to worry about screen resolution.
  • You can select more than one value for each filter.

Since I’ll have several lists with filters in the application I’m developing, I decided to implement a templated control that I can reuse everywhere in my app.

Let’s see how I did it.

Control parts

The control is basically composed by two parts:


<Style TargetType="local:ListFiltersControl">
        <Setter Property="Width" Value="300" />
        <Setter Property="Height" Value="40" />
        <Setter Property="Background" Value="{StaticResource ListFiltersBackgroundBrush}"/>
        <Setter Property="Foreground" Value="{StaticResource ListFiltersForegroundBrush}"/>
        <Setter Property="TabNavigation" Value="Once"/>
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
        <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled"/>
        <Setter Property="ScrollViewer.VerticalScrollMode" Value="Auto"/>
        <Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="True"/>
        <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False"/>
        <Setter Property="ScrollViewer.BringIntoViewOnFocusChange" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:ListFiltersControl">
                    <Grid x:Name="LayoutRoot">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
 
                        <ToggleButton x:Name="PART_ExpandCollapseButton" Style="{StaticResource MainFiltersButtonStyle}" Content="#Filters" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Grid.Row="0" />
                        <Popup x:Name="PART_MainFiltersPopup" IsLightDismissEnabled="False" Grid.Row="1">
                            <Border Width="{TemplateBinding Width}" BorderBrush="{StaticResource ListFiltersPopupBorderBrush}" BorderThickness="{StaticResource ListFiltersPopupBorderThickness}" Background="{StaticResource ListFiltersMainFilterItemBackgroundBrush}" HorizontalAlignment="Stretch" Height="597">
                                <ScrollViewer AutomationProperties.AccessibilityView="Raw" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" Foreground="{ThemeResource ComboBoxPopupForegroundThemeBrush}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" MinWidth="{ThemeResource ComboBoxPopupThemeMinWidth}" VerticalSnapPointsType="OptionalSingle" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" VerticalSnapPointsAlignment="Near" ZoomMode="Disabled">
                                    <ItemsControl ItemsSource="{TemplateBinding Filters}">
                                        <ItemsControl.ItemTemplate>
                                            <DataTemplate>
                                                <StackPanel HorizontalAlignment="Stretch">
                                                    <Button Content="{Binding Text}" Style="{StaticResource MainFilterItemButtonStyle}">
                                                        <Button.Flyout>
                                                            <Flyout Placement="Right" FlyoutPresenterStyle="{StaticResource SubFiltersFlyoutPresenterStyle}">
                                                                <ItemsControl ItemsSource="{Binding Items}" ItemTemplate="{StaticResource SubFilterMultiSelectionItemTemplate}" />
                                                            </Flyout>
                                                        </Button.Flyout>
                                                    </Button>
                                                    <ItemsControl ItemsSource="{Binding SelectedItemsToShow}">
                                                        <ItemsControl.ItemTemplate>
                                                            <DataTemplate>
                                                                <Button Content="{Binding Text}" Style="{StaticResource MainFilterItemSelectedButtonStyle}" Command="{Binding RemoveFilterCommand}" CommandParameter="{Binding}" />
                                                            </DataTemplate>
                                                        </ItemsControl.ItemTemplate>
                                                    </ItemsControl>
                                                    <Button Content="{Binding MoreItemsSelectedDescription}" Style="{StaticResource MainFilterItemMoreItemsSelectedButtonStyle}" Visibility="{Binding HasMoreItemsSelected, ConverterParameter=VisibleIfTrue, Converter={StaticResource VisibilityConverter}}">
                                                        <Button.Flyout>
                                                            <Flyout Placement="Right" FlyoutPresenterStyle="{StaticResource SubFiltersFlyoutPresenterStyle}">
                                                                <ItemsControl ItemsSource="{Binding Items}" ItemTemplate="{StaticResource SubFilterMultiSelectionItemTemplate}" />
                                                            </Flyout>
                                                        </Button.Flyout>
                                                    </Button>
                                                </StackPanel>
                                            </DataTemplate>
                                        </ItemsControl.ItemTemplate>
                                    </ItemsControl>
                                </ScrollViewer>
                            </Border>
                        </Popup>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
  • PART_ExpandCollapseButton: it’s a ToggleButton that open and close a popup with the list of filters. I decided to use a ToggleButton instead of a standard Button because it has Checked and Unchecked states and it was easier to apply a custom style to show up and down arrow.

    The ToggleButton, however, cannot have an attached flyout so I had to manually open and close a Popup control.
  • PART_MainFiltersPopup: it contains the list of available filters and for each filter, the list of selected values. Each filter is implemented as a standard Button with an attached Flyout that contains the list of available values. In the sample application I implemented multi selection so I used a ToggleButton for each filter value.
To set the list of available filters I added Filters dependency property bound to the ItemsControl ItemsSource that is placed inside the popup.

I also added IsOpen dependency property to programmatically open or close the popup from the app that uses the control.

Notifying when filters selection is changed

Each filter item has ApplyFilter and RemoveFilter commands that are used to apply or clear it.

I had to set commands for each filter item since AncestorType and AncestorLevel binding is not yet available in Windows 8.1 (like I used to do in Silverlight 5).

To keep track of which filters are selected, I used a private variable _selectedSubFilters that is an ObservableCollection of ListFiltersControlSubFilter; this collection is updated when a filter is applied or cleared.

To get the list of items to show we typically invoke a web service. To reduce the number of service calls, I decided to use Reactive Extensions framework to apply throttling and notifying filters selection only after a small amount of time after the last filter has been applied.

Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(eh => _selectedSubFilters.CollectionChanged += eh, eh => _selectedSubFilters.CollectionChanged -= eh)
                                       .Throttle(TimeSpan.FromSeconds(0.650))
                                       .ObserveOn(SynchronizationContext.Current)
                                       .Subscribe(ApplyFilters);


I monitor _selectedSubFilters CollectionChanged event and after 650 msec from the last time the event has been raised, ApplyFilters function is called.

In this function I check if selected filters are changed from the last time (for simplicity I compare Json serialized strings of previous and new selected filters object) and then I raise SelectedFiltersChanged event passing the list of selected filters in the args of the event.

Sample application

Here you can download a sample application that simulate recipes list filters like in Food & Drink app:
The app uses MVVM pattern through Prism and Unity.

Conclusions

I think this new way of applying filters to lists is more flexible than having a list of combo boxes.

If you have any suggestions you can send me an email at andreadomenichini@live.com


Change log

Version 1.0.1 (15/02/2014)

  • Added SelectionMode property to ListFiltersControlMainFilter class in order to manage single and multiple selection (I used a ListView control instead of a list of ToggleButton of main filter items).
  • Added MenuHeight property that allows to set the height of the main filters flyout.