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:
- Acquiring relevant data based on the current context (for example a user search query using the search charm)
- 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
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.
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
- These views look like the Metro file picker
- They are great to help a user browse content that is arbitrarily organized (such as a documents library)
- To build these views, use a shallow query that returns files and folders and order the results by item type (folders first, then files)
- 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.
- To build these views, use a folder query that returns the top-level groups to display
- 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. |
MusicView |
256 |
A square thumbnail at size 256x256 (when possible). The system caches this thumbnail size, which improves performance. |
DocumentsView/ ListView |
40 |
A fixed aspect ratio square thumbnail at size 40x40 (when possible). |
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.
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.
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>
<CollectionViewSource
x:Name="itemsViewSource"/>
UserControl.Resources>
<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:
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.
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 |
Developer quickstart |
Explains how to create a generic ListView |
|
Code sample |
Demonstrates usage of the StorageDataSource and GetVirtualizedFilesVector to create a view based on local files |
|
Code snippets |
Provides sample item templates for ListView |
|
API reference |
StorageDataSource and FileInformationFactory API reference |