Part of Metro style design is building clean views that allow content to shine and help users accomplish their tasks. We explore in particular how you can create beautiful galleries with content from user’s files and folders on the local file system. Being able to display local content is a key requirement for many gallery apps that let users browse and consume their content – photos, videos, music, or documents. Windows 8 provides tools to do this in a simple, efficient, and customizable way.

To show this I’ll take the example of the PhotoJournal app, a connected photo journal where users can view and manage their photos and videos using a timeline view. In the next figure, you can see the landing page of the app. A typical example of a view that this app creates over the file system is a timeline view, which shows photos recently published in the app and stored in the app’s local data folder. Another is a search results view that lets the user find specific photos. In this post, I’ll go through the two steps the app takes to build these views:

  1. Acquiring relevant data based on the current context (for example a user search query using the search charm)
  2. Using the built-in StorageDataSource control to bind this data to a predefined formatting that is tailored to the content and easy for the user to consume

The PhotoJournal app uses image gallery views to let users share their photos and comment on them
Figure 1: The PhotoJournal app 

Getting the data

Building a file query with the right options

The first step in creating the view is preparing the data source. The best way to create a view based on the file system is to construct a file query that returns the files you want. File queries are a fast, easily customized way to access local data, and are guaranteed to always be up-to-date with the latest file system state.

Defining the data set

As you can see in this chart, users can have a lot of data, especially digital images, which are accumulated over time due to the wide availability of digital cameras and camera phones. Because of that volume of data, you need to design views that make sense in a wide range of scenarios – from an average user who has a few thousand pictures to a media enthusiast with tens of thousands. Doing this means taking advantage of filters and pivots to keep each view to a manageable size. I recommend that you don’t default your gallery view to a flat list of all the user’s content, and instead spend some time thinking about what your query should look like. It could be as simple as following the file system’s folder hierarchy to segment the collection into smaller sets.

users keep a lot of data; the average user has a few tens of thousands of media files on their computer, mostly images, and the top 10% of users owns almost more than 70,000 media files
  Figure 2: Per user file counts per content type 

When deciding what the query should be, think about what your app is best at, and how its strengths apply to the scenario that you want to enable: what makes a particular item important to the user in the context of your app? PhotoJournal is best at dynamically showing photos and using additional data about them, so the date the photo was taken, and context around it (such as location), are useful pivots to surface to the user – either as filters, as a way to arrange items, or labels.

Here are a few examples of views that you can use in a gallery style app. You can create all of these using APIs provided by Windows 8.

  • Hierarchical views of files and folders
  • Aggregated views based on metadata
    • These views re-arrange files into groups based on metadata
    • They are great to visualize media in a way that makes sense for the user: by album, by artist, by tag, etc.
  • Flat filtered views
    • These views present a list of files, abstracting the folder hierarchy
    • They are great for displaying filtered sets of items that the user can easily parse at a glance – for example showing a search view filtered by keyword
    • To build these views, use a deep query that returns files

Implementing the query

You can implement all the examples we just looked at using Advanced Query Syntax (AQS), a language that is supported by the search box in the File Explorer and the File search feature. AQS is a powerful tool that you can use to narrow down results based on metadata and content. Using this tool you can retrieve only the data that you need, and order it in the way you want it with the query APIs. AQS is backed by the power of the system index, so you can get results and display your views much faster than if you manually filtered the file set.

In our case, PhotoJournal focuses on recent photos and uses AQS to show on its home page only images that were taken less than a month ago. Using a flat list sorted by date is what makes sense for the app to give the feeling of a timeline.

JS

// Create a new file query from the pictures library and apply the AQS filter
var picturesLibrary = Windows.Storage.KnownFolders.picturesLibrary;
var options = new Windows.Storage.Search.QueryOptions(Windows.Storage.Search.CommonFileQuery.orderByDate, [".jpg", ".png"]);
// Use the app search filter to enforce business logic
options.applicationSearchFilter = "System.Photo.DateTaken:> System.StructuredQueryType.DateTime#LastMonth";
var fileQuery = picturesLibrary.createFileQueryWithOptions(options);

C#

// Create a new file query from the pictures library and apply the AQS filter
var fileTypeFilter = new string[] { ".jpg", ".png" };
var options = new Windows.Storage.Search.QueryOptions(Windows.Storage.Search.CommonFileQuery.OrderByDate, fileTypeFilter);
// Use the appl search filter to enforce business logic
options.ApplicationSearchFilter = "System.Photo.DateTaken:> System.StructuredQueryType.DateTime#LastMonth";
var fileQuery = Windows.Storage.KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(options);

On the other hand, in the search results view, what matters is what the user wants to find, so instead of using the same date filter as the timeline view, the app uses the user query string to filter the query by keyword and show only relevant data. Here is how to do this.

JS

// Create a new file query from the pictures library and apply the AQS filter
var picturesLibrary = Windows.Storage.KnownFolders.picturesLibrary;
var options = new Windows.Storage.Search.QueryOptions(Windows.Storage.Search.CommonFileQuery.orderByDate, ["*"]);
// Use the text provided in the search box as the user search filter
options.userSearchFilter = queryText;
var fileQuery = picturesLibrary.createFileQueryWithOptions(options);

C#

// Create a new file query from the pictures library and apply the AQS filter
var fileTypeFilter = new string[] { "*" };
var options = new Windows.Storage.Search.QueryOptions(Windows.Storage.Search.CommonFileQuery.OrderByDate, fileTypeFilter);
// Use the application search filter to enforce business logic
options.UserSearchFilter = queryText;
var fileQuery = Windows.Storage.KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(options);

Creating a data source suited for display

After you set up the query, it’s a matter of a few lines of code to make it ready for display in a ListView control. The StorageDataSource is the simplest way to accomplish this. Creating a StorageDataSource requires you to provide a little bit of additional info, based on what you want your view to look like.

Choosing the right thumbnail mode

When the time comes to create the data source, you need to decide what kind of thumbnails you want. Windows Runtime lets you choose among several thumbnail modes depending on what you want to do with the thumbnail. The thumbnail mode tailors some parameters such as cropping. PhotoJournal is a gallery of images, so it specifies the picturesView mode in the StorageDataSource constructor, which causes all thumbnails to be returned cropped to the same 0.7 aspect ratio – using the golden ratio for a harmonious view. picturesView is the canonical way to display photos in a Metro style app.

Picking a thumbnail size

The thumbnail size indicates the intended size of the thumbnail. The system caches thumbnails at pre-defined sizes that are used throughout the system: using one of these sizes will help your app adopt a Metro style look and feel, and avoid the performance cost of an image resize. Here is a list of the available thumbnail modes and the sizes we recommend using for each of them.

Thumbnail mode

Recommended request size

Return value

PicturesView / VideosView

190

A fixed aspect ratio cropped thumbnail at size 190x130 (when possible). The system caches this thumbnail size, which improves performance.

The thumbnail is cropped at a .7 aspect ratio 

MusicView

256

A square thumbnail at size 256x256 (when possible). The system caches this thumbnail size, which improves performance.

The album art is cropped to a square thumbnail 

DocumentsView/ ListView

40

A fixed aspect ratio square thumbnail at size 40x40 (when possible).

The icon is the file association icon registered on the system, displayed on a square color plate 

SingleItem

16, 32, 48, 96, 256, 1024

A thumbnail of the file at original aspect ratio, where the requested size is the size of the largest edge. Six different cached sizes are available for best performance.

Table 1: Recommended thumbnail sizes

You can easily ensure that the thumbnails you use with your data source work regardless of the current scale factor, by passing the Windows.Storage.FileProperties.ThumbnailOptions.useCurrentScale option to the data source constructor. Therefore, it is sufficient to request the size that corresponds to your app’s layout at the 100% scale factor. Specifying a size of zero indicates that you don’t plan on using thumbnails in your view, and so they are not retrieved.

Building the data source

Now that I know what I want my collection to look like, I can go ahead and create the data source object that the ListView control can use to display my gallery.

JS

// Set data source options
var dataSourceOptions = {
// Options to retrieve thumbnails
mode: Windows.Storage.FileProperties.ThumbnailMode.picturesView,
requestedThumbnailSize: 190, // 0 means that thumbnails should not be retrieved
thumbnailOptions: Windows.Storage.FileProperties.ThumbnailOptions.useCurrentScale
};

// Create the data source
var dataSource = new WinJS.UI.StorageDataSource(fileQuery, dataSourceOptions);

We expect that many apps will use similar StorageDataSource configurations, reflecting common patterns that work well with each kind of content, like pictures. So to make your life easier we’ve provided presets that configure the StorageDataSource with these optimal configurations, all in one line of code. In the case of PhotoJournal, I can obtain the same results as the code we just looked at with much less code, by using the default configuration for pictures.

// Shorthand datasource (similar shorthand constructors are available for Videos,
// Music and Documents)
var dataSource = new WinJS.UI.StorageDataSource("Pictures");

C#

In C#, the semantics of creating a data source are a little bit different, and instead of creating a StorageDataSource you will use the FileInformationFactory class. The next code shows how you can use this object to get a virtualized files vector, which is the construct that you can use with a GridView control.

var fileInformationFactory = new FileInformationFactory(
fileQuery,
Windows.Storage.FileProperties.ThumbnailMode.PicturesView,
size,
Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale,
true);

Custom data sources

The StorageDataSource control and FileInformationFactory work great if your app needs only common organization pivots that are built into the file system. If you need to apply business logic to the data being displayed, it is possible to create your own datasource on the file system.

Examples of cases where your app would need to implement its own data source are if you are aggregating data across multiple sources, for example the cloud, a custom database and the file system. Additionally, if you need to filter, sort or group items based on app-specific business logic (for example based on the date a photo was published in PhotoJournal), you would also need to have your own data source. You can still use data model APIs to query for the data, but you need to add your own layer of filtering, and manage how data is virtualized and returned to the ListView control, which requires a little bit more work.

Formatting the data

While getting only relevant content in your view is a prerequisite to avoid overwhelming the user, designing a beautiful and polished presentation of the data is where you app can shine and differentiate itself. As always, how exactly you present your data depends on your app’s purpose, and the kind of content you want to display.

Designing a pictures gallery

PhotoJournal is all about photos, so it follows key guidelines informed by the metro principles: putting content first, minimizing distractions, and providing a fast and fluid experience. You can see these guidelines in action in the File Picker:

  • In a picture-centric view, what matters is the image thumbnail
  • Users can usually identify a photo at a glance – often you don’t need to show a file name. In fact, not showing it can reduce distraction and clutter
  • Use the PicturesView thumbnail mode to get thumbnails with consistent aspect ratio for a clean and harmonious view
  • Use simple placeholders to provide visual feedback while images are loaded in the view – this makes your app look faster
  • Only show text that is useful for the user (for example properties that match the user’s search query in a search results view)
  • Provide additional info in a flyout that the user can access through a press and hold gesture

Here is what the PhotoJournal search view looks like when we implement these principles.

The PhotoJournal search view uses images heavily to help users quickly identify content, but also highglights properties matched by the user’s search query to show why each image result is returned  Figure 3: Emphasis on images, highlighting properties matched by the user’s search query

Designing beyond pictures

Other types of content follow different rules. For example, in a music view, what matters to quickly identify an item is the song name, album title or artist name, but it’s also valuable to show album art for quick browsing. Documents, on the other hand, don’t usually yield themselves to rich graphical representation because they are text-heavy. In that case, use an item template that gives more importance to text and item details.

If your view follows a hierarchical model where files and folders are shown together, make sure to visually differentiate the two kinds of items. To that end, you can follow the patterns set by the Windows file picker. The user sees these patterns throughout the system, which makes them familiar and recognizable. Using them helps your app seamlessly integrate with Windows 8.

File picker thumbnail
Figure 4: Using overlays in the File Picker to differentiate folders

When you decide what you want your view to look like, you can move on to actually displaying the view.

Displaying the view

A key concept when displaying the view is the notion of item template. When creating a view, all the items displayed are formatted into a common template, which is populated with the item’s info (thumbnail, name, date, etc.)

In JavaScript

In JavaScript, with the ListView control, the best way to style your templates is to use CSS based on ListView classes.

.imageGallery .win-container
{
margin-right: 10px;
margin-bottom: 10px;
}

.imageGallery .win-item
{
width: 190px;
height: 130px;
overflow: hidden;
background-color: #333333;
}

.imageGallery .win-item img {
width: 190px;
height: 130px;
overflow: hidden;
}

The most common approach to render items in your view is to write an item template method which the ListView control calls to create each item in the view. That method is called once per item, whenever the item needs to be shown on screen. This is called programmatic rendering. For each item, you programmatically create DOM elements on the fly.

Managing thumbnail quality can be complex. The first time the user views a photo on the system, Windows returns a low-resolution fast thumbnail to improve responsiveness, and then follows-up with a high-resolution thumbnail. If you don’t want to deal with this complexity and had rather let the system handle these events, you can use the StorageDataSource control’s LoadThumbnail helper function, which abstracts away the details and simply ensures that the requested thumbnail is inserted in the provided image element. Rendering items becomes as simple as a few lines of code:

function storageRenderer(itemPromise, element) {
var img, itemStatus;
if (element === null) {
// dom is not recycled, so create inital structure
element = document.createElement("div");
element.className = "FileTemplate";
element.appendChild(document.createElement("img"));
}
img = element.querySelector("img");
img.style.opacity = 0;

return {
// returns the placeholder
element: element,
// and a promise that will complete when the item is fully rendered
renderComplete: itemPromise.then(function (item) {
// now do cheap work (none here, so we return item ready)
return item.ready;
}).then(function (item) {
// wait until item.ready before doing expensive work
return WinJS.UI.StorageDataSource.loadThumbnail(item, img).then(function (image) {
// perform any operation that requires the thumbnail to be available
});
})
};
}

When your styles and renderer are ready, all you have to do is initialize the ListView control, and your gallery app is all set:

var container = document.getElementById("listviewDiv");
var listViewOptions = {
itemDataSource: dataSource,
itemTemplate: storageRenderer,
layout: new WinJS.UI.GridLayout(),
selectionMode: "single"
};

var listViewControl = new WinJS.UI.ListView(container, listViewOptions);

Note: another approach similar to the XAML example we’ll look at in a moment is available to JavaScript developers through declarative rendering with HTML markup – you can check out the StorageDataSource sample for an example. Declarative rendering is an easier approach, but programmatic rendering affords you more flexibility.

In XAML

In XAML, you use an approach that is a little different from the JavaScript method outlined earlier. This approach consists in writing down some XAML markup representing the template that you want to use, and to bind this template to the GridView or ListView control. This is called declarative rendering. In that case, the ListView itself takes care of generating the elements for all items in the view, and uses the bindings to populate them with the correct data. You can bind any property exposed by the FileInformation object to the control.

XAML template markup

<UserControl.Resources>
<local:ThumbnailConverter x:Key="thumbnailConverter"/>
<DataTemplate x:Key="Custom190x130ItemTemplate">
<Grid Width="190" Height="130">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" Width="190" Height="130">
<Image Source="{Binding Path=Thumbnail, Converter={StaticResource thumbnailConverter}}" Width="190" Height="130"/>
</Border>
</Grid>
</DataTemplate>

<!-- Collection of items displayed by this page -->
<CollectionViewSource
x:Name="itemsViewSource"/>
</UserControl.Resources>

<!-- Horizontal scrolling grid -->
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Items"
Grid.Row="1"
Margin="0,-4,0,0"
Padding="116,0,40,46"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
ItemTemplate="{StaticResource Custom190x130ItemTemplate}"
SelectionMode="None"/>

If a property needs to be processed before being bound (for example the thumbnail stream, which cannot directly be bound to an Image object but needs to be decoded first), you can declare value converters to perform that processing.

C# value converter

internal class ThumbnailConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string culture)
{
if (value != null)
{
var thumbnailStream = (IRandomAccessStream)value;
var image = new BitmapImage();
image.SetSource(thumbnailStream);

return image;
}

return DependencyProperty.UnsetValue;
}

public object ConvertBack(object value, Type targetType, object parameter, string culture)
{
throw new NotImplementedException();
}
}

You can then setup the GridView with the VirtualizedItemsVector that you obtain from the FileInformationFactory object.

itemsViewSource.Source = fileInformationFactory.GetVirtualizedFilesVector();

Beyond the initial view

You are now ready to go and create great views of the file system – but there is more beyond just creating one view. An app is great when it responds to the user’s needs and tailors every screen to what the user is trying to do. For example, offering options to pivot data or using semantic zoom are two ways to enhance user experience by writing some additional code.

Pivoting data

In the example of the PhotoJournal app, I could decide to add pivots around camera model or date. These pivots allow me to restrict the breadth of the file query using AQS, and to always show the user a manageable number of pictures. Every time the pivot changes, all I need to do is run a new query and swap the data sources in my ListView control, then discard the old query and data source to avoid growing the app’s resource consumption too much. I can easily add such pivots to the search view that I showed earlier. Here’s the result:

Add pivots, such as tabs or drop-down menus with various filter options, at the top of your search view to help users filter down content Figure 5: Add pivots to your view to help users filter down content

Semantic zooming

Semantic zoom is another great way to make your app shine with a little bit of extra code. If pivoting doesn’t work well for your app, or even pivoted views still contain large numbers of items, semantic zoom lets you show an aggregate view of the content that the user can evaluate at a glance.

Use semantic zoom as a way to present high-level aggregate data in a format that uses your app’s theme – for example, in PhotoJournal, a timeline indicating the count of photos posted every day as a bar graphFigure 6: Semantic zooming

In closing

In this post, you’ve seen how to use the StorageDataSource and VirtualizedItemsVector with JavaScript or XAML controls to create rich views of the file system for your gallery app. Some things to keep in mind:

  • Carefully choose the data you are showing in your view and query the file system accordingly
  • Consider content type and alignment with other Windows experiences when styling your view
  • Use the flexibility of the rendering pipeline to express your business logic when drawing items
  • Go the extra mile with pivots and semantic zoom

You’re now ready to go create a great gallery app!

--Marc Wautier, Program Manager, Windows User Experience

 

Resources

Link

Type

Highlights

Quickstart: adding a ListView

Developer quickstart

Explains how to create a generic ListView

StorageDataSource and GetVirtualizedFilesVector sample

Code sample

Demonstrates usage of the StorageDataSource and GetVirtualizedFilesVector to create a view based on local files

Item templates for grid layouts

Code snippets

Provides sample item templates for ListView

StorageDataSource and FileInformationFactory

API reference

StorageDataSource and FileInformationFactory API reference