Visually Located

XAML and GIS

Getting and Setting the SeletedIndex (visible section) of the Hub, now with Binding!

In my last post I talked about how you can restore the last visible section of the Hub. In that post I talked about the lack of the SelectedIndex and SelectedItem properties in the Hub control. These properties were in the Panorama control. Not having these properties means setting the visible section from a view model requires access to the Hub. This is not ideal. When functionality is not available, create it!

When you want to add functionality to a control there are two basic solutions.

  1. Extend the control by creating a new one.
  2. Extend the control with attached properties

The first solution is generally accomplished by inheriting from the control itself. The second is most often solved with a behavior. Whenever possible I prefer option 1 over option 2. The downside to option 1 is adding more and more functionality trying to come up with a good name for your control.

Extending existing controls is really easy. There [usually] is not a need to create a new style for the control. We can easily add new dependency properties to the control.

public class SelectionHub : Hub
{
    public int SelectedIndex
    {
        get { return (int)GetValue(SelectedIndexProperty); }
        set { SetValue(SelectedIndexProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for SelectedIndex.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedIndexProperty =
        DependencyProperty.Register(
        "SelectedIndex", 
        typeof(int), 
        typeof(SelectionHub), 
        new PropertyMetadata(0, OnSelectedIndexChanged));
 
    private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // TODO
    }
}

When extending a control, you’ll want to override the OnApplyTemplate method to plug in your custom functionality. For adding the ability to add set or get the functionality, we’ll want to listen to when the section changes. In the last post I described how you can use the SelectionsInViewChanged event to be notified when the visible sections change. An odd thing about this event that Atley Hunter found is that it will not fire when the Hub has two sections. If we want a solution to work for all hubs, we need another event to hook into. If we view the style of the Hub control, we’ll see that is has a ScrollViewer control that aids moving content.

<Canvas Grid.RowSpan="2">
...
</Canvas>
<ScrollViewer x:Name="ScrollViewer" HorizontalScrollMode="Auto" HorizontalSnapPointsType="None" HorizontalAlignment="Left" HorizontalScrollBarVisibility="Hidden" Grid.RowSpan="2" Template="{StaticResource ScrollViewerScrollBarlessTemplate}" VerticalScrollBarVisibility="Disabled" VerticalScrollMode="Disabled" ZoomMode="Disabled">
    <ItemsStackPanel  x:Name="Panel" CacheLength="6" Orientation="{TemplateBinding Orientation}"/>
</ScrollViewer>
<Canvas Grid.Row="0">
...
</Canvas>

The ScrollViewer has the ViewChanged event that we can hook into to tell when the visible section changes! In the event we can what the current selected index is by checking the first index of the SectionsIdView list.

protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();
 
    var scroller= GetTemplateChild("ScrollViewer") as ScrollViewer;
    if (scroller == null) return;
 
    scroller.ViewChanged += ScrollerOnViewChanged;
}
 
private void ScrollerOnViewChanged(object sender, ScrollViewerViewChangedEventArgs scrollViewerViewChangedEventArgs)
{
    _settingIndex = true;
    SelectedIndex = Sections.IndexOf(SectionsInView[0]);
    _settingIndex = false;
}

When the SelectedIndex changes, we want to set the visible section. The SelectedIndex can change from binding, from code behind, or from the user swiping. the _settingIndex property above is to prevent trying to change the visible section when setting the SelectedIndex when swiping.

private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var hub = d as SelectionHub;
    if (hub == null) return;
 
    // do not try to set the section when the user is swiping
    if (hub._settingIndex) return;
 
    // No sections?!?
    if (hub.Sections.Count == 0) return;
 
    var section = hub.Sections[hub.SelectedIndex];
    hub.ScrollToSection(section);
}

Using this new hub control is simple

<controls:SelectionHub SelectedIndex="{Binding SeletedSectionIndex}"
                       Header="application name" 
                       Background="{ThemeResource HubBackgroundImageBrush}">
    <!-- sections -->
</controls:SelectionHub>

If you prefer option 2 for extending controls, then can easily be converted to a behavior. First, add a reference to the Behaviors SDK.

BehaviorReference

The key difference is subscribing to the ScrollViewer events when the associated object is attached.

public void Attach(DependencyObject associatedObject)
{
    AssociatedObject = associatedObject;
    var hub = associatedObject as Hub;
    if (null == hub) return;
 
    _scroller = hub.GetChildOfType<ScrollViewer>();
    if (_scroller == null)
    {
        hub.Loaded += OnHubLoaded;
    }
    else
    {
        _scroller.ViewChanged += ScrollerOnViewChanged;
    }
}
 
private void OnHubLoaded(object sender, RoutedEventArgs routedEventArgs)
{
    var hub = (Hub)sender;
 
    _scroller = hub.GetChildOfType<ScrollViewer>();
    if (_scroller != null)
    {
        _scroller.ViewChanged += ScrollerOnViewChanged;
        hub.Loaded -= OnHubLoaded;
    }
}

The behavior listens to the loaded event of the hub because it is possible that the hub it attached after the hub has loaded or before. 99% of the time, it will be before the hub had loaded, but you never know. From there it is pretty much the same.

You can download a Universal app solution in which the Windows Phone project uses the behavior and the Windows project uses the new control. Either solution can be used, the choice of which was used for the the project was random.

Restore the last visible Hub Section with the DefaultSectionIndex

I previously blogged about transitioning your app from the Panorama control to the new Hub control in Windows Phone 8.1 runtime apps. That post served as a starting point for moving away from the Silverlight Panorama control. As time goes by we find more things that need to be moved over. One of those is the ability to set (or get) the the selected PanoramaItem. This is a really important feature. According to the design guidelines for the Panorama.

For subsequent visits, the user should be taken back to the pane where they left off when user revisits the same Panorama control.

The design guidelines for the Hub has the following

For subsequent visits to the same hub control, take the user back to the section where they left off.

You can see this experience in the People hub.

In the Panorama control you could get the current visible item through either the SelectedIndex or the SelectedItem property, or be notified when the visible item changes with the SelectionChanged event. Following the guideline was a little hard however because the best way to ensure the pano opened to the correct page is with the DefaultItem property. The reason this was hard is because this excepts a PanoramoItem rather than an index. With an index, it could easily be saved and retrieved. With an item, it’s a little harder to save this information off and then get it the next time the app opened. While this was difficult, it was still doable.

The Hub control keeps this same workflow but has made an improvement. Instead of setting a default item, you now set an index with the DefaultSectionIndex property. The downside is the Hub control does not have a SelectedIndex or SelectedItem property. Fear not! The cheese was not removed, just moved. You can get the currently visible section with the SectionsInView property. This property returns an IList<HubSection> in which the first item in the list is the visible section and the second item in the list is the section that is peeking in on the right. With the SectionsInView and the Sections property, you can get the currently selected index.

var section = hub.SectionsInView[0];
var selectedIndex = hub.Sections.IndexOf(section);
Now that you have the visible section, you can save that off whenever you need to navigate away from your hub page. This can be done when navigating to a new page, or when the exiting the app. The hub even fires an event when the sections in view change.
private void OnSectionsInViewChanged(object sender, SectionsInViewChangedEventArgs e)
{
    var section = Hub.SectionsInView[0];
    ViewModel.DefaultIndex = Hub.Sections.IndexOf(section);
}

The viewmodel would set the index in the local settings so that it can easily be retrieved the next time the app is opened.

public class HubViewModel
{
    public int DefaultIndex
    {
        get
        {
            object defaultIndex;
            if (ApplicationData.Current.LocalSettings.Values.TryGetValue("defaultIndex", out defaultIndex))
            {
                return (int) defaultIndex;
            }
            return 0;
        }
        set { ApplicationData.Current.LocalSettings.Values["defaultIndex"] = value; }
    }
}

And then we bind to the property in our Hub

<Hub x:Name="Hub" x:Uid="Hub" Header="application name" Background="{ThemeResource HubBackgroundImageBrush}"
     DefaultSectionIndex="{Binding DefaultIndex}"
     SectionsInViewChanged="OnSectionsInViewChanged">
    <!-- setions -->
</Hub>

Now every time the user opens the app the hub will be on the last page they left from!

Download the sample solution and try it out.

Sorting mail from a contact list to a folder in outlook.com

Outlook.com offers the ability to add rules to your inbound traffic to help filter your mail. If you are part of a mailing list that gets a lot of emails, you may want to take advantage of these rules. Adding a rule to put these emails into a separate folder will help reduce “clutter” from your inbox folder. To accomplish this go to the settings of outlook.com and click Manage rules

MailSettings

From there click the New button, that will bring up the create rule page. Create a new rule when the email is sent to the mailing list.

CreateRule

Select a new folder for the mail to go into.

RuleAction

Click Save and from then on any mail to the mailing list will go to that folder!

Bind a collection of items to the Windows Phone MapControl

With every major version of Windows Phone comes a new way to work with maps. Keeping up with all of these changes has, honestly, been a hassle. Windows Phone 8.1 continues this trend with the new MapControl. It does improve from Windows Phone 8 in a lot of ways. One of those areas is that you no longer need another toolkit to perform basic map functionality. In Windows Phone 8 you needed the Windows Phone Toolkit to do things like add map elements to the map. Now this functionality is part of the core functionality.

To bind a collection of items to the new MapControl you use the MapItemsControl within the MapControl itself.

<maps:MapControl x:Name="Map" MapServiceToken="abcdef-abcdefghijklmno">
    <maps:MapItemsControl ItemsSource="{Binding Locations}">
    </maps:MapItemsControl>
</maps:MapControl>

The MapItemsControl is just a DependencyObject that has the ability to set items, through the Items and ItemsSource properties, and define what they look like with the ItemTemplate property. With the MapItemsControl ItemTemplate you can set your map icons to anything you want! Any XAML can be placed on your map!

Using an Image:

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Image Source="Assets/mappin.png" Height="25"/>
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

MapPinImage

Using a Path:

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Path Width="28.5" Height="38" Stretch="Fill" Fill="Red" Data="F1 M 36.4167,19C 44.2867,19 50.6667,24.6711 50.6667,31.6667C 50.6667,32.7601 50.5108,33.8212 50.2177,34.8333L 36.4167,57L 22.6156,34.8333C 22.3225,33.8212 22.1667,32.7601 22.1667,31.6667C 22.1667,24.6711 28.5466,19 36.4167,19 Z M 36.4167,27.7083C 34.2305,27.7083 32.4583,29.4805 32.4583,31.6667C 32.4583,33.8528 34.2305,35.625 36.4167,35.625C 38.6028,35.625 40.375,33.8528 40.375,31.6667C 40.375,29.4805 38.6028,27.7083 36.4167,27.7083 Z "/>
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

MapPinPath

This will put map icons on your map, but will not place them where they need to be (unlike the images I provided). To set the map location of the icons, you use the attached Location property on the MapControl.

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Image Source="Assets/mappin.png" Height="25"
               maps:MapControl.Location="{Binding Geopoint}" />
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

This example assumes that the object you are binding to has a Geopoint property that returns a Geopoint. When map icons are placed, they are anchored at the top left of the icon. This works well if you use an icon that points to the top left, otherwise you will want to change it. To change the anchor point of the icon, you set the NormalizedAnchorPoint attached property on the MapControl. Set this with a Point with values between 0 and 1. Where 0,0 is the top left and 1,1 is the bottom right.

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Image Source="Assets/mappin.png" Height="25"
               maps:MapControl.Location="{Binding Geopoint}" 
               maps:MapControl.NormalizedAnchorPoint=".5,1" />
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

Here is a weird bug that I swear was not there when I started working with the new maps back in May. Fellow MVP and mapping addict, Joost van Schaik, noticed that setting this value in xaml does nothing. It will always be anchored at the top left. He found that if you bind to an Anchor property then it works well.

<maps:MapItemsControl.ItemTemplate>
    <DataTemplate>
        <Image Source="Assets/mappin.png" Height="25"
               maps:MapControl.Location="{Binding Geopoint}" 
               maps:MapControl.NormalizedAnchorPoint="{Binding Anchor}" />
    </DataTemplate>
</maps:MapItemsControl.ItemTemplate>

Hopes this helps get you going with the new maps in Windows Phone 8.1. You can download a sample projectsample project to test as well.

Implementing truly timed Windows Phone app trials with Azure Mobile Services

An awesome part of Windows Phone and Windows Store apps is the ability to offer a trial version to users before they decide to purchase the app. This gives uses an opportunity to try the app for free before paying. There are different ways to offer trials. You can limit functionality while in trial mode. You can offer unlimited trials, allowing the user to use the app forever, or you can allow the user to use the app for limited time period. For Windows Store apps, you can specify in the Store how long the user is allowed to try the app.

StoreTrial

Windows Phone apps do not offer this capability. When implementing timed trials in apps, a common task for app developers is to store a value within the app for when the app was first opened.

Note: As this is relevant for both Silverlight and Runtime apps, this blog will contain code for both. The code samples will switch back and forth.

public static bool IsTrialExpired()
{
    // Silverlight example
    var appSettings = IsolatedStorageSettings.ApplicationSettings;
    DateTime expirationDate;
    if (appSettings.TryGetValue("TrialExpirationDate", out expirationDate) == false)
    {
        // first time app opened
        expirationDate = DateTime.Now + TimeSpan.FromDays(7);
        appSettings["TrialExpirationDate"] = expirationDate;
        appSettings.Save();
    }
    return expirationDate > DateTime.Now;
}

You would then use this method along with the LicenseInformation class.

// Silverlight apps
var licenseInfo = new LicenseInformation();
var isTrial = licenseInfo.IsTrial();
 
// Runtime apps
var licenseInfo = CurrentApp.LicenseInformation
var isTrial = licenseInfo.IsTrial;
 
if(isTrial)
{
    if(IsTrialExpired())
    {
        // tell user they can no longer use app
    }
    // possibly limit some functionality
}
 

This has been the solution for many apps. The problem with this solution is that the user can uninstall the app and reinstall and get the trial all over again! To overcome this, your app needs to use a service to store user information and check that service for if the trial has expired. There are many cloud based services available to you that are perfect for storing information like this. Two great services are Parse and Azure Mobile Services. This blog post will cover using Azure Mobile Services.

Using a service allows your app to check if the user opened the app at any time. If the user uninstalls the app and reinstalls it, they will now be able to get the trial again. The first step is to set up a mobile service in Azure.

CreateNewService

Give the service a name. Name the service something that matches your apps name. If you do not already have a database in azure, create a new free db

NewMobileService

When the service is created, click on it, then the Data tab and add new table

AddTable

Give the table a name that maps to the app itself. This table will be in a database that you will use across multiple apps, so you don’t want something generic like UserInfo, or TrialUser.

CreateNewTable

Now for some code. First we will create a class that will represent our new table in Azure Mobile Services.

class MyAppUserInfo
{
    public string Id { get; set; }
    public DateTimeOffset TrialExpirationDate { get; set; }
}

Next let’s create a new interface and class that will allow us to check the service if the users trial has expired.

interface ITrialService
{
    /// <summary>
    /// Get or sets the amount of time the user is allowed to use the trial.
    /// </summary>
    TimeSpan TrialPeriod { get; set; }
 
    /// <summary>
    /// Gets a value indicating if the trial has expired for the given userId.
    /// </summary>
    /// <param name="userId">A unique idenitfier for a user</param>
    /// <returns>A task that will complete when the data is retrieved.</returns>
    Task<bool> IsExpired(string userId);
}

The interface is pretty simple. One property to specify how long the trial should last and one method to see if the trial has expired. For the class, the main this to implement it the IsExpiredAsync method.

Note: Ensure that you add the WindowsAzure.MobileServices nuget package.

class AzureTrialService : ITrialService
{
    private readonly IMobileServiceClient _service;
 
    public AzureTrialService(IMobileServiceClient service)
    {
        _service = service;
        TrialPeriod = TimeSpan.FromDays(7);
    }
 
    public TimeSpan TrialPeriod { get; set; }
 
    public async Task<bool> IsExpiredAsync(string userId)
    {
        bool isExpired = false; 
 
        IMobileServiceTable<MyAppUserInfo> table = _service.GetTable<MyAppUserInfo>(); 
        var users = await table.Where(trialUser => trialUser.Id == userId).ToListAsync();
        var user = users.FirstOrDefault();
        if (user == null)
        {
            // new user, add it
            var trialExpirationDate = DateTimeOffset.Now + TrialPeriod;
            await table.InsertAsync(new MyAppUserInfo { Id = userId, TrialExpirationDate = trialExpirationDate });
        }
        else
        {
            // mobile services will deserialize as local DateTime
            isExpired = user.TrialExpirationDate < DateTimeOffset.Now;
        }
         
        return isExpired;
    }
}

So the IsExpiredAsync method will check Azure Mobile Services for the given userId. If none is returned, it will add a new user. Every time after that it will check the expiration date with the current time.

This current implementation is dependent on the MyAppUserInfo class, which means that we cannot reuse this class for multiple apps. I lie to reuse my code rather than copy and pasting code. Let’s make a modification to the interface and class that allows this to be used for any app.

First we’ll create an abstract class for the user information. We need an abstract class because I was not able to get data from the service using an interface. Might be user error, or an issue with the SDK. Our MyAppUserInfo will implement this abstract class.

/// <summary>
/// Represents information for a trial user.
/// </summary>
abstract class TrialUser
{
    /// <summary>
    /// Gets or sets the id for the user.
    /// </summary>
    public abstract string Id { get; set; }
 
    /// <summary>
    /// Gets or sets the date the trial will expire for the user.
    /// </summary>
    public abstract DateTimeOffset TrialExpirationDate { get; set; }
}

Next we will modify the IsExpiredAsync method to allow for a generic parameter, but must be of type ITrialUser

interface ITrialService
{
    //other stuff
 
    Task<bool> IsExpiredAsync<TUser>(string userId) where TUser : TrialUser, new();
}
 
class AzureTrialService : ITrialService
{
    // other stuff
 
    public async Task<bool> IsExpiredAsync<TUser>(string userId) where TUser : TrialUser, new()
    {
        bool isExpired = false;
 
        IMobileServiceTable<TUser> table = _service.GetTable<TUser>();
        var users = await table.Where(userInfo => userInfo.Id == userId).ToListAsync();
        var user = users.FirstOrDefault();
        if ((user == null)
        {
            // new user, add it
            var trialExpirationDate = DateTimeOffset.Now + TrialPeriod;
            user = new TUser { Id = userId, TrialExpirationDate = trialExpirationDate };
            await table.InsertAsync(user);
        }
        else
        {
            // mobile services will deserialize as local DateTime
            isExpired = user.TrialExpirationDate < DateTimeOffset.Now;
        }
 
        return isExpired;
    }
}

Now we have a service that can be used across multiple apps to test if a trial has expired for a user. Let’s take a step back for a moment. Our initial goal here was to stop storing trial expiration locally and start using a service. Check! This new service does accomplish that, but now it checks the service every time the app is opened. There is no need to delay the opening of the app any more than is needed. We can get the expiration information the first time the app is opened and save it to use on later opens. We’ll again modify the IsExpiredAsync method

public async Task<bool> IsExpiredAsync<TUser>(string userId) where TUser : TrialUser, new()
{
    bool isExpired = false;
 
    object dateVal;
    if (ApplicationData.Current.LocalSettings.Values.TryGetValue("trialExpiration", out dateVal) == false)
    {
        // get user from server
        IMobileServiceTable<TUser> table = _service.GetTable<TUser>();
 
        var users = await table.Where(userInfo => userInfo.Id == userId).ToListAsync();
        var user = users.FirstOrDefault();
 
        if (user == null)
        {
            // new user, add it
            var trialExpirationDate = DateTimeOffset.Now + TrialPeriod;
            dateVal = trialExpirationDate;
            user = new TUser { Id = userId, TrialExpirationDate = trialExpirationDate.ToUniversalTime() };
            await table.InsertAsync(user);
        }
        else
        {
            // mobile services will deserialize as local DateTime
            dateVal = user.TrialExpirationDate;
        }
        ApplicationData.Current.LocalSettings.Values["trialExpiration"] = dateVal;
    }
    var expirationDate = (DateTimeOffset)dateVal;
    isExpired = expirationDate < DateTimeOffset.Now;
 
    return isExpired;
}

Now when the user opens the app the second time, they will not hit the service. If they uninstall the app and reinstall, it will check the service once.

The TrialService does still rely on the date the user has on their phone. So there are two possible problems. The first is if the user sets the date ahead when they first open the app. The second is if they set the date back before opening the app. While this is probably not likely, you can prevent this if you wish.

To ensure the expiration date cannot be tampered we can set the date in the service script when a new row is created. Go to your mobile service in the Azure portal and select the Data tab and select your table. Select the Script tab and edit the insert script.

script

function insert(item, user, request) {
    var today = new Date();
    var expiration = new Date(today.setDate(today.getDate() + 7));
    item.TrialExpirationDate = expiration;
    request.execute();
}

This script will set the expiration date to be one week after the row is created.

Next select the read script. We will check the expiration when getting the user information.

function read(query, user, request) {
 
    request.execute({ 
    success: function(results) {
        results.forEach(function(r) {
            // IMPORTANT: Note the case of trialexpirationdate  
            r.isExpired = r.trialexpirationdate < new Date();
            });
        request.respond(); 
        }
    }); 
}

If you take this approach, you will need a new IsExpired property on your user info class and you can then remove the TrialExpirationDate property. You can also remove the TrialPeriod property from ITrialService since the period is being set on the server.

So now we have a way to get if the trial has expired, how do we get the id for the user? For Silverlight apps (Windows Phone 8) you can get the the anonymous id for a user through the UserExtendedProperties class.

string userId = UserExtendedProperties.GetValue("ANID2") as string;

For Windows Phone 8.1 Runtime apps, there is no such property (that I have found!). I have seen some posts about using various device ids that are available, but this would mean the user could try the app on a number of devices. So far the best approach I have seen is from Dave Smits on the msdn forums. The solution is to use roaming settings to store a new unique ID.

object userId;
if (ApplicationData.Current.RoamingSettings.Values.TryGetValue("MyAppUserID", out userId) == false)
{
    userId = Guid.NewGuid();
    ApplicationData.Current.RoamingSettings.Values["MyAppUserID"] = userId;
}

Now with all of this information we can determine if the users trial has expired.

string userId = GetUserId();
var azureClient = new MobileServiceClient(
    "https://myapptrials.azure-mobile.net/", "YourSecretKey"));
ITrialService trialService = new AzureTrialService(azureClient);
bool isExpired = await trialService.IsExpiredAsync<MyAppUserInfo>(userId);
if(isExpired)
{
    // tell the user the trial is expired
    // most likely prompt user to purchase app
}

This TrialService should still be used along with the LicenseInfomation class to first check if the user is using the trial version of the app. You can get the complete code for the service on github.

It's important to also note that all records in Azure Mobile Services have a __createdAt column that can be used as well. You can get the property in your class by adding a JsonPropertyAttribute to a property with the name "__createdAt".

Disabling tilt on a ListView or GridView

I have mentioned before that the tilt animation is enabled by default in Windows Phone 8.1 Runtime apps, but what happens when you want to display a collection of items but do not want to enable tilt? You may not want to enable tilt because there is no extra action that happens when you tap an item. As with most things, there are a few options here.

The first is to not use a ListView/GridView at all. Instead just use an ItemsControl to display your items. This is great provided you do not have a lot of items you need to show. ItemsControl is not virtualized like the ListView/GridView so it uses a lot of memory when displaying a lot of items. This is my go to control for displaying a small collection of items that have no action associated with them.

If you still want to use the ListView or GridView control then there is a simple way to disable the animation. You will need to modify the style of the ListViewItem or GridViewItem. For this post I will demonstrate using the ListViewItem, but everything applies to the GridView as well.

First create a copy of the default style of the ListViewItem, you can accomplish this by right clicking on the ListView within the Visual Studio (or Blend) designer or in the document view.

Select: Edit Additional Templates –> Edit Generated Item Container (ItemContainerStyle) –> Edit a Copy…

Edit Template

Name the new style “NonTiltListViewItemStyle”

StyleName

This will add the style for the ListViewItem as well as a bunch of color and size resources. The key now is to find the use of the PointerUpThemeAnimation and PointerDownThemeAnimation.and remove them. You’ll remove the VisualStateGroup.Transitions from the CommonStates group, remove the Storyboard from the Pressed state. If you have enabled multi select, remove PointerDownThemeAnimation from the CheckboxPressed state.

You now have a style that will not have the tilt animation when tapped!

Tilt animation for Windows Phone Runtime

In a previous post I talked about some of the awesome animations and transitions available in Window Phone 8.1 Runtime apps. These animations and transitions were previously only available with a second SDK like the Windows Phone Toolkit or Telerik’s phone controls. One of the most used animations from both toolkit was the tilt animation. This animation shows a user that what they are touching can be tapped and will probably do something when they do tap. I also previously blogged about how you can enable the tilt animation for “unselectable” items. I am happy to say that the tilt animation is now built into all “tappable” controls without having to do anything!

So you would think that would be the end of the post right? I just said “it just works”. This is true for things like ListView/GridView/Buton, but what about when you want to display a StackPanel with a few items in it and those items should be tiltable? Some work is needed to get this working and there are a few ways to accomplish this.

Wrap content in ListViewItem

Much like my previous post, you can easily enable tilt on “normal” items by simply wrapping the elements with a ListViewItem!

<!-- enable tilt by wrapping with a ListViewItem -->
<ListViewItem>
    <StackPanel Margin="0,0,0,9.5">
        <TextBlock Text="Name" Style="{ThemeResource ControlContextualInfoTextBlockStyle}"/>
        <TextBlock Text="Shawn Kendrot" Style="{ThemeResource BodyTextBlockStyle}"/>
    </StackPanel>
</ListViewItem>

This works because of the default style of the ListViewItem uses the PointerUpThemeAnimation and PointerDownThemeAnimation.

Start Storyboard on PointerPressed/Released

A second option to to listen to the PointerPressed and PointerReleased events of an element and play a Storyboard with the

<!-- enable tilt by listening to pointer events -->
<Grid PointerPressed="OnStoryboardGridPressed" 
      PointerReleased="OnStoryboardGridReleased">
    <Grid.Resources>
        <Storyboard x:Key="TiltDownStory">
            <PointerDownThemeAnimation TargetName="LovesPanel" />
        </Storyboard>
        <Storyboard x:Key="TiltUpStory">
            <PointerUpThemeAnimation TargetName="LovesPanel" />
        </Storyboard>
    </Grid.Resources>
    <StackPanel x:Name="LovesPanel" Margin="0,0,0,9.5">
        <TextBlock Text="Loves" Style="{ThemeResource ControlContextualInfoTextBlockStyle}"/>
        <TextBlock Text="XAML" Style="{ThemeResource BodyTextBlockStyle}"/>
    </StackPanel>
</Grid>
private void OnStoryboardGridPressed(object sender, PointerRoutedEventArgs e)
{
    var panel = ((FrameworkElement)sender);
    var story = (Storyboard)panel.Resources["TiltDownStory"];
    PlayStory(story);
}
 
private void OnStoryboardGridReleased(object sender, PointerRoutedEventArgs e)
{
    var panel = ((FrameworkElement)sender);
    var story = (Storyboard)panel.Resources["TiltUpStory"];
    PlayStory(story);
}
 
private void PlayStory(Storyboard story)
{
    // stop just in case it is already playing
    story.Stop();
    story.Begin();
}

When you tap the element, it will play the story.

Use VisualStates

A final option is to create a new control that will use visual states to play the animations. You will need to create you own control, either a UserControl or a custom control. When the element is pressed or released you will change the visual state of the control. For this example I used a UserControl.

protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
    base.OnPointerPressed(e);
    _isPressed = true;
    ChangeState("PointerDown");
}
 
protected override void OnPointerReleased(PointerRoutedEventArgs e)
{
    base.OnPointerReleased(e);
    _isPressed = false;
    ChangeState("PointerUp");
}
 
private void ChangeState(string state, bool useTransitions = true)
{
    VisualStateManager.GoToState(this, state, useTransitions);
}

Define the states in your XAML

<Grid> 
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="TapStates">
            <VisualState x:Name="Normal" />
            <VisualState x:Name="PointerDown">
                <Storyboard>
                    <PointerDownThemeAnimation TargetName="RootPanel" />
                </Storyboard>
            </VisualState>
            <VisualState x:Name="PointerUp">
                <Storyboard>
                    <PointerUpThemeAnimation TargetName="RootPanel" />
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <Grid x:Name="RootPanel">
        <!-- place content here -->
    </Grid>
</Grid>

So when you have content that needs to show the tilt animation, you have three ways to do this. You can download my complete transition/animation sample to see these three examples.

Hope that helps!

Alternatives to OpacityMask

In Windows Phone Silverlight apps you had the ability to take a png image and change the color of the image with an OpacityMask. This is a really handy tool to have when you want to show different state to a user or use one image and have it work with different backgrounds. Where is an example:

<StackPanel Orientation="Horizontal">
    <Image Source="Assets/appbar.book.png" Width="72" Height="72"></Image>
    <Rectangle Fill="Red" Width="72" Height="72" >
        <Rectangle.OpacityMask>
            <ImageBrush ImageSource="Assets/appbar.book.png" Stretch="Fill"/>
        </Rectangle.OpacityMask>
    </Rectangle>
    <Border Background="{StaticResource PhoneForegroundBrush}" Width="72" Height="72" >
        <Rectangle Fill="{StaticResource PhoneBackgroundBrush}" >
            <Rectangle.OpacityMask>
                <ImageBrush ImageSource="Assets/appbar.book.png" Stretch="Fill"/>
            </Rectangle.OpacityMask>
        </Rectangle>
    </Border>
</StackPanel>

This will give you the following three images:

OpacityIcons

This works really well except that it is pretty intensive on your phone to do this so I do not recommend doing a lot of these!

While this works great in Windows Phone Silverlight apps, this functionality does not exist in Windows Runtime (XAML apps for Windows Store and Phone 8.1). So what should you do? You have a few options.

BitmapIcon

If you wish to continue to use the image, you can use a BitmapIcon to display the image and change the color. Set the image with the UriSource property and then set the Foreground to the desired color! Be careful using the BitmapIcon because it will fill up all the space it is given so you’ll want to set the height and width of it, or it’s container explicitly.

<StackPanel Orientation="Horizontal" Height="72">
    <BitmapIcon UriSource="Assets/appbar.book.png" Margin="12"/>
    <BitmapIcon UriSource="Assets/appbar.book.png" Margin="12"
            Foreground="Red"/>
    <Border Background="{StaticResource PhoneForegroundBrush}">
        <BitmapIcon UriSource="Assets/appbar.book.png" Margin="12"
            Foreground="{StaticResource PhoneBackgroundBrush}"/>
    </Border>
</StackPanel>
Use an image vector

The first option is to use a vector of the image. Once you have the vector data, you display it in a Path element.

You most likely already have access to the vector of the image. You may have created the image with Photoshop, or you are using one of the awesome icons from the Modern UI Icon pack. The zip from Modern UI Icons contains the XAML needed to create an image, but you can also get it from the website! Using that XAML for the Path I can then recreate the three images above:

<StackPanel Orientation="Horizontal">
    <Path Width="42" Height="33.7913" Stretch="Fill" Fill="White" 
          Margin="12"
          Data="F1 M 22,46.9996C 26.4235,48.3026 34.4825,48.8053 37.2083,52.2153L 37.2083,32.9996C 34.4826,29.5896 26.4235,29.0869 22,27.7839L 22,46.9996 Z M 22,24.3078L 22,24.028C 26.4235,25.331 34.4825,25.8337 37.2083,29.2437L 38,29.4716L 38.7917,29.2157C 41.5174,25.8057 49.5765,25.303 54,24L 54,24.2798C 55.2286,24.6498 56,24.9716 56,24.9716L 56,27.9716L 59,26.8258L 59,50.9716C 59,50.9716 41.1667,52.2216 38,57.7633L 37.9999,57.7913C 34.8333,52.2496 17,50.9996 17,50.9996L 17,26.8538L 20,27.9996L 20,24.9996C 20,24.9996 20.7714,24.6778 22,24.3078 Z M 23.5,44.506L 23.5,41.3844C 27.269,42.243 32.4604,42.8187 35.5,44.7496L 35.5,47.8712C 32.4604,45.9402 27.269,45.3646 23.5,44.506 Z M 23.5,39.1212L 23.5,35.9996C 27.269,36.8582 32.4604,37.4338 35.5,39.3648L 35.5,42.4864C 32.4604,40.5554 27.269,39.9798 23.5,39.1212 Z M 23.5,33.6344L 23.5,30.5128C 27.269,31.3714 32.4604,31.947 35.5,33.878L 35.5,36.9996C 32.4604,35.0686 27.269,34.493 23.5,33.6344 Z M 54,46.9716L 54,27.7559C 49.5765,29.0589 41.5174,29.5616 38.7917,32.9716L 38.7917,52.1873C 41.5175,48.7773 49.5765,48.2746 54,46.9716 Z M 52.5,44.478C 48.731,45.3366 43.5395,45.9122 40.5,47.8432L 40.5,44.7216C 43.5395,42.7906 48.731,42.215 52.5,41.3564L 52.5,44.478 Z M 52.5,39.0932C 48.731,39.9518 43.5395,40.5274 40.5,42.4584L 40.5,39.3368C 43.5396,37.4058 48.731,36.8302 52.5,35.9716L 52.5,39.0932 Z M 52.5,33.6064C 48.731,34.465 43.5395,35.0406 40.5,36.9716L 40.5,33.85C 43.5395,31.919 48.731,31.3434 52.5,30.4848L 52.5,33.6064 Z "/>
    <Path Width="42" Height="33.7913" Stretch="Fill" Fill="Red" 
          Margin="12"
          Data="F1 M 22,46.9996C 26.4235,48.3026 34.4825,48.8053 37.2083,52.2153L 37.2083,32.9996C 34.4826,29.5896 26.4235,29.0869 22,27.7839L 22,46.9996 Z M 22,24.3078L 22,24.028C 26.4235,25.331 34.4825,25.8337 37.2083,29.2437L 38,29.4716L 38.7917,29.2157C 41.5174,25.8057 49.5765,25.303 54,24L 54,24.2798C 55.2286,24.6498 56,24.9716 56,24.9716L 56,27.9716L 59,26.8258L 59,50.9716C 59,50.9716 41.1667,52.2216 38,57.7633L 37.9999,57.7913C 34.8333,52.2496 17,50.9996 17,50.9996L 17,26.8538L 20,27.9996L 20,24.9996C 20,24.9996 20.7714,24.6778 22,24.3078 Z M 23.5,44.506L 23.5,41.3844C 27.269,42.243 32.4604,42.8187 35.5,44.7496L 35.5,47.8712C 32.4604,45.9402 27.269,45.3646 23.5,44.506 Z M 23.5,39.1212L 23.5,35.9996C 27.269,36.8582 32.4604,37.4338 35.5,39.3648L 35.5,42.4864C 32.4604,40.5554 27.269,39.9798 23.5,39.1212 Z M 23.5,33.6344L 23.5,30.5128C 27.269,31.3714 32.4604,31.947 35.5,33.878L 35.5,36.9996C 32.4604,35.0686 27.269,34.493 23.5,33.6344 Z M 54,46.9716L 54,27.7559C 49.5765,29.0589 41.5174,29.5616 38.7917,32.9716L 38.7917,52.1873C 41.5175,48.7773 49.5765,48.2746 54,46.9716 Z M 52.5,44.478C 48.731,45.3366 43.5395,45.9122 40.5,47.8432L 40.5,44.7216C 43.5395,42.7906 48.731,42.215 52.5,41.3564L 52.5,44.478 Z M 52.5,39.0932C 48.731,39.9518 43.5395,40.5274 40.5,42.4584L 40.5,39.3368C 43.5396,37.4058 48.731,36.8302 52.5,35.9716L 52.5,39.0932 Z M 52.5,33.6064C 48.731,34.465 43.5395,35.0406 40.5,36.9716L 40.5,33.85C 43.5395,31.919 48.731,31.3434 52.5,30.4848L 52.5,33.6064 Z "/>
    <Border Background="{StaticResource PhoneForegroundBrush}" Width="72" Height="72" >
        <Path Width="42" Height="33.7913" Stretch="Fill" Fill="{StaticResource PhoneBackgroundBrush}" 
          Margin="12"
          Data="F1 M 22,46.9996C 26.4235,48.3026 34.4825,48.8053 37.2083,52.2153L 37.2083,32.9996C 34.4826,29.5896 26.4235,29.0869 22,27.7839L 22,46.9996 Z M 22,24.3078L 22,24.028C 26.4235,25.331 34.4825,25.8337 37.2083,29.2437L 38,29.4716L 38.7917,29.2157C 41.5174,25.8057 49.5765,25.303 54,24L 54,24.2798C 55.2286,24.6498 56,24.9716 56,24.9716L 56,27.9716L 59,26.8258L 59,50.9716C 59,50.9716 41.1667,52.2216 38,57.7633L 37.9999,57.7913C 34.8333,52.2496 17,50.9996 17,50.9996L 17,26.8538L 20,27.9996L 20,24.9996C 20,24.9996 20.7714,24.6778 22,24.3078 Z M 23.5,44.506L 23.5,41.3844C 27.269,42.243 32.4604,42.8187 35.5,44.7496L 35.5,47.8712C 32.4604,45.9402 27.269,45.3646 23.5,44.506 Z M 23.5,39.1212L 23.5,35.9996C 27.269,36.8582 32.4604,37.4338 35.5,39.3648L 35.5,42.4864C 32.4604,40.5554 27.269,39.9798 23.5,39.1212 Z M 23.5,33.6344L 23.5,30.5128C 27.269,31.3714 32.4604,31.947 35.5,33.878L 35.5,36.9996C 32.4604,35.0686 27.269,34.493 23.5,33.6344 Z M 54,46.9716L 54,27.7559C 49.5765,29.0589 41.5174,29.5616 38.7917,32.9716L 38.7917,52.1873C 41.5175,48.7773 49.5765,48.2746 54,46.9716 Z M 52.5,44.478C 48.731,45.3366 43.5395,45.9122 40.5,47.8432L 40.5,44.7216C 43.5395,42.7906 48.731,42.215 52.5,41.3564L 52.5,44.478 Z M 52.5,39.0932C 48.731,39.9518 43.5395,40.5274 40.5,42.4584L 40.5,39.3368C 43.5396,37.4058 48.731,36.8302 52.5,35.9716L 52.5,39.0932 Z M 52.5,33.6064C 48.731,34.465 43.5395,35.0406 40.5,36.9716L 40.5,33.85C 43.5395,31.919 48.731,31.3434 52.5,30.4848L 52.5,33.6064 Z "/>
    </Border>
</StackPanel>

The vector data will scale up or down very nicely as well so you do not have to worry about multiple images!

Fonts

The second option is to use one of the built in symbol fonts like Segoe UI Symbol. This font allows for many options of symbols. It’s best to use a Character Map to see what is available. Because this option uses text elements, it is very easy to scale the icon up or down using the FontSize property!

<StackPanel Orientation="Horizontal">
    <TextBlock FontFamily="Segoe UI Symbol" Text="&#xE114;" 
               FontSize="64" Margin="6"/>
    <TextBlock FontFamily="Segoe UI Symbol" Text="&#xE114;" 
               Foreground="Red" 
               FontSize="64"  Margin="6"/>
    <Border Background="{StaticResource PhoneForegroundBrush}">
        <TextBlock FontFamily="Segoe UI Symbol" Text="&#xE114;"
                   Foreground="{StaticResource PhoneBackgroundBrush}"
               FontSize="64" Margin="6"/>
    </Border>
</StackPanel>

FontIcons

A third option (which goes along with the second) is to create your own font. You can use Fontastic (thanks to Christopher Maneu for this suggestion) which will take SVG and convert to a font. Another option for creating custom fonts is Type 3.2 (thanks to Matt Duffield for this suggestion). I have not used these options, but I’m sure they work fine!

SymbolIcon

The last option is to use the SymbolIcon. This is a great option if you are using a “standard” icon. The SymbolIcon allows you to set a Symbol with plain, readable text!

<StackPanel Orientation="Horizontal">
    <SymbolIcon Symbol="Camera" Margin="12"/>
    <SymbolIcon Symbol="Camera" Margin="12" Foreground="Red"/>
    <Border Background="{StaticResource PhoneForegroundBrush}" Height="52">
        <SymbolIcon Symbol="Camera" Margin="12" Foreground="{StaticResource PhoneBackgroundBrush}"/>
    </Border>
</StackPanel>

SymbolIcon

The unfortunate part of the SymbolIcon is that you cannot set a size to it. You can however use the ViewBox control to scale the icon without any loss of quality!

<Viewbox Width="100">
    <SymbolIcon Symbol="Camera" Margin="12"/>
</Viewbox>

Hope that helps you out on your next project!

Give your lists the space they deserve

This blog serves as a public service announcement to give your ListBox, ListView, ItemsControl, whatever you choose the space that they deserve. I have seen a lot of apps (and often forget to do this myself!) that do not extend their lists all the way to the right of the page and keep the page too close to the bottom. It is easy to fall into this trap because of the defaults within Visual Studio. Take a look at the following examples:

gap

Notice that huge gap to the side? Now take a look at the settings apps

No gap

Look mom! No gap! So what causes this gap? It’s the default template for pages in Visual Studio. When you create a new page for Windows Phone Silverlight apps you get the following

<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
</Grid>

Notice the side margin of 12? This is a design guideline to leave the gap, but lists give you this buffer as well! You get the same XAML for Windows Phone Runtime app, but 9.5 instead of 12 for the margin. So, when you create a new page that will have a list in it, remove that right margin!

<!--ContentPanel - it has a ListBox so no right margin! -->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,0,0">
    <ListBox>
    </ListBox>
</Grid>

Now our app behaves just like the built in apps!

no gap now

The second space you want to give your apps is space at the bottom of the list. When you use a list it will expand to all the space it is given. The downside to this is when you scroll to the bottom of the list, the content is at the very bottom! This makes it a little hard to see. Take a look at the following examples.

No gap
gap

Notice the nice gap at the bottom of the settings app. This is a lot easier on your eyes. This one is easy to solve as well. Simply add some padding to the bottom of your list.

<ListView Padding="0,0,0,72">
</ListView>

now with bottom gap

If you want you can create a style to use everywhere.

Take a few seconds and update your apps with these gaps.

Creating a feathered (aka staggered) page transition

One of the most known page transitions on Windows Phone is the feather (or staggered item) transition. This is the transition you see when opening the mail app and the messages feather, or stagger in. This animation is also seen on the home page on the phone when the tiles stagger in. You usually see this transition whenever there is a list of items shown by any of the built in apps. This transition was previously only available through toolkits like the Windows Phone Toolkit or Telerik’s toolkit. Now this transition is available out of the box for Windows Phone Runtime apps.

For background on page transitions in Windows Phone 8.1 Runtime apps, see my previous post.

To get started, simply create a new Windows Phone Runtime app. Page transitions are enabled by default for all Runtime apps! To use this transition, you’ll want to set the page transition to use the NavigationThemeTransition with the CommonNavigationTransitionInfo. Make sure to set IsStaggeringEnabled to true for the CommonNavigationTransitionInfo.

<Page.Transitions>
    <TransitionCollection>
        <NavigationThemeTransition>
            <NavigationThemeTransition.DefaultNavigationTransitionInfo>
                <CommonNavigationTransitionInfo IsStaggeringEnabled="True"/>
            </NavigationThemeTransition.DefaultNavigationTransitionInfo>
        </NavigationThemeTransition>
    </TransitionCollection>
</Page.Transitions>

Next, you’ll want to add the CommonNavigationTransitionInfo.IsStaggerElement attached property to any element that you want to stagger in. You will generally place this in the ItemTemplate of the ListView or GridView that you are displaying. You read that right. This transition works in both the ListView and GridView. If you use this on the template of a GridView, then you will see the tiles of your grid stagger in!

First a ListView:

<ListView ItemsSource="{Binding Items}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel Margin="0,0,0,9.5"
                        CommonNavigationTransitionInfo.IsStaggerElement="True">
                <TextBlock Text="{Binding Title}"
                           Style="{ThemeResource ListViewItemTextBlockStyle}"/>
                <TextBlock Text="{Binding Subtitle}" Style="{ThemeResource BaseTextBlockStyle}"/>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

And a GridView:

<GridView ItemsSource="{Binding}">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Rectangle Width="100" Height="100" Fill="{Binding}" 
                       Margin="0,0,9.5,9.5"
                       CommonNavigationTransitionInfo.IsStaggerElement="True"/>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Notice that the StackPanel and Rectangle both set CommonNavigationTransitionInfo.IsStaggerElement="True".

That’s all you need to do for your app to begin using this awesome transition!

Feather Transition

Download a complete sample (with many more samples!) of these transitions.