Archive

Author Archive

Smooth flick and zoom with Viewport control for Windows Phone

April 8, 2014 6 comments

One of best features of Windows Phone is its fluid way for flick and zoom of images in your photo albums. Yet, most of apps that show images aren’t implemented that way of handling images (e.g. Facebook, Twitter, WhatsApp, etc.) and flicking images is pretty laggy in my opinion (actually it just a pan). However, there is simple solution for such cases and it is called Viewport control.

Essentially, Viewport control for Windows Phone handles pan and flick gestures of its content and provides smooth bounce effect when you flick content (image, canvas, etc.) that is larger than size of screen or viewport itself. You can see that effect in photo album or in Internet Explorer.

Implementation is very simple: just add Viewport control to page, put Image inside it and set Viewport Bound property to size of the image.

<Grid x:Name="LayoutRoot" Background="White">
    <ViewportControl Bounds="0,0,1249,1920">
        <Image Source="/Assets/Saga_14.jpg" />
    </ViewportControl>
</Grid>

And that is it. See how it looks:

Zoom

In order to implement zoom we have to manualy resize image and set view point in ManipulationDelta event of Viewport control. Set XAML code in this way:

<Grid x:Name="LayoutRoot" Background="White">
    <ViewportControl x:Name="viewport" 
        ManipulationDelta="viewport_ManipulationDelta" 
        ManipulationCompleted="viewport_ManipulationCompleted" >
        <Image x:Name="image" Source="/Assets/Saga_14.jpg" 
               CacheMode="BitmapCache" Loaded="image_Loaded" 
               Stretch="None" Source="/Assets/Saga_14.jpg" />
    </ViewportControl>
</Grid>

In ManipulationDelta event we check for Pinch event (e.PinchManipulation != null) and use e.PinchManipulation.CumulativeScale value to calculate new size of image. Also we use e.PinchManipulation.Original.Center value to calculate new point of view for image, sice we want to zoom in and out at that place that pinch actualy occured on screen. To do that we have to do several things:

  • Get offset of image in viewport (or how much is image moved in viewport using transform.Matrix.OffsetX and transform.Matrix.OffsetY values):

     MatrixTransform transform = image.TransformToVisual(viewport) as MatrixTransform;

  • Calculate center of pinch gesture on image (not screen):

     Point pinchCenterOnImage = transform.Transform(e.PinchManipulation.Original.Center);

  • Calculate and set new origin point of viewport:

     Point newOriginPoint = new Point(relativeCenter.X * newWidth – pinchCenterOnImage.X, relativeCenter.Y * newHieght – pinchCenterOnImage.Y);
viewport.SetViewportOrigin(newOriginPoint); 

 

Complete code behind in ManipulationDelta event looks like:

private double m_Zoom = 1;
private double m_Width = 0;
private double m_Height = 0;

private void viewport_ManipulationDelta(object sender, 
System.Windows.Input.ManipulationDeltaEventArgs e)
{
    if (e.PinchManipulation != null)
    {

        double newWidth, newHieght;


        if (m_Width < m_Height)  // box new size between image and viewport
        {
            newHieght = m_Height * m_Zoom * e.PinchManipulation.CumulativeScale;
            newHieght = Math.Max(viewport.ActualHeight, newHieght);
            newHieght = Math.Min(newHieght, m_Height);
            newWidth = newHieght * m_Width / m_Height;
        }
        else
        {
            newWidth = m_Width * m_Zoom * e.PinchManipulation.CumulativeScale;
            newWidth = Math.Max(viewport.ActualWidth, newWidth);
            newWidth = Math.Min(newWidth, m_Width);
            newHieght = newWidth * m_Height / m_Width;
        }


        if (newWidth < m_Width && newHieght < m_Height)
        {
            MatrixTransform transform = image.TransformToVisual(viewport) 
              as MatrixTransform;
            Point pinchCenterOnImage = 
              transform.Transform(e.PinchManipulation.Original.Center);
            Point relativeCenter = 
              new Point(e.PinchManipulation.Original.Center.X / image.Width, 
              e.PinchManipulation.Original.Center.Y / image.Height);
            Point newOriginPoint = new Point(
              relativeCenter.X * newWidth - pinchCenterOnImage.X, 
              relativeCenter.Y * newHieght - pinchCenterOnImage.Y);
            viewport.SetViewportOrigin(newOriginPoint);
        }

        image.Width = newWidth;
        image.Height = newHieght;

        // Set new view port bound
        viewport.Bounds = new Rect(0, 0, newWidth, newHieght);
    }
}

private void viewport_ManipulationCompleted(object sender, 
System.Windows.Input.ManipulationCompletedEventArgs e)
{
    m_Zoom = image.Width / m_Width;
}

 

Here is zoom in action

 

You can download sample code on http://code.msdn.microsoft.com/wpapps/Smooth-flick-and-zoom-with-7760c7f7

Categories: Windows Phone Tags: , , , , ,

Accent Windows Phone List Items With Binding Convertors

Imagine scenario in your Windows Phone application where you have to display some items in ListBox (or LongListSelector) control in phone accent color based on some property in bounded collection. This can be easily implemented with Binding Convertors which purpose is to convert data on its way from the source to the target and (if necessary) back the other way. Binding converters are simple classes of type IValueConverter, an interface that requires only two methods named Convert and ConvertBack. Convert handles the data conversion from the source to the target, and ConvertBack handles the conversion going in the other direction for a TwoWay binding. If you ever worked with popular Coding4fun toolkit, you probably noticed (and used, I hope) BooleanToVisibilityConverter, StringToVisibilityConverter, ThemedImageConverter, etc. More about Binding Converters and their use with Coding4fun toolkit you can found on following address.

Let’s go back to our example. If we want converter that will change style of UI element to Accent color of your phone, based on some Boolean value, first we have to create new converter class:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace AccentListItems.Wp8
{
    public class AccentConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool)
            {
                bool isAccent = (bool)value;
                if (isAccent)
                    return App.Current.Resources["PhoneTextAccentStyle"] as Style;
                else
                    return App.Current.Resources["PhoneTextNormalStyle"] as Style;
            }
            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}

Covert method is very simple; if source value if true return PhoneTextAccentStyle style, otherwise return PhoneTextNormalStyle style. Note that you can extend this method to work with any other type of source data.

Now we can use this converter in in our XAML page. First add the converter’s prefix namespace to page (put it in phone:PhoneApplicationPage section):

xmlns:local="clr-namespace:AccentListItems.Wp8"

Next step is to define converter’s instance in page. Put following code in page resources section:

<phone:PhoneApplicationPage.Resources>
    <local:AccentConverter x:Key="AccentConverter"/>
</phone:PhoneApplicationPage.Resources>

 

Now we have to add ListBox. This example is for WP8, so we will add LongListSelector. I also recommend you to use Coding4fun LongListSelector in WP7 version, but you can use ListBox control also (at the end of this post you will find download links for complete solutions; WP7 version uses ListBox).

<phone:LongListSelector
    Name="listBoxCities" 
    ItemTemplate="{StaticResource CityItemTemplate}">
</phone:LongListSelector>

 

In resource section of page add list items template:

<phone:PhoneApplicationPage.Resources>
    <local:AccentConverter x:Key="AccentConverter"/>

    <DataTemplate x:Key="CityItemTemplate">
        <StackPanel Orientation="Vertical" >
            <TextBlock 
                FontSize="26"
                Style="{Binding Path=IsAccent, Converter={StaticResource AccentConverter} }"
                Text="{Binding Name}" />
            <TextBlock 
                Margin="12,0,0,12"
                Style="{Binding Path=IsAccent, Converter={StaticResource AccentConverter} }"
                FontSize="18" 
                Text="{Binding Country}" />
        </StackPanel>
    </DataTemplate>
</phone:PhoneApplicationPage.Resources>

 

In our example we will bind list to collection of cities, which is defined in following class:

using System;
using System.ComponentModel;

namespace AccentListItems.Wp8
{
    public class City
    {
        public string Name { get; set; }
        public string Country { get; set; }
        public string IsAccent { get; set; }
    }
}

 

Color of binded cities in list box will change to Accent color if value of IsAccent property of city is true.

Let’s see complete code behind this page:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using Microsoft.Phone.Controls;
using System.Collections.ObjectModel;

namespace AccentListItems.Wp8
{
    public partial class MainPage : PhoneApplicationPage
    {
        private ObservableCollection<City> cities;

        public MainPage()
        {
            InitializeComponent();
            this.InitCities();
        }

        private void InitCities()
        {
            cities = new ObservableCollection<City>();
            cities.Add(new City() { Name = "Sacramento", Country = "USA", IsAccent = false });
            cities.Add(new City() { Name = "Toulouse", Country = "France", IsAccent = true });
            cities.Add(new City() { Name = "Augsburg", Country = "Germany", IsAccent = true });
            cities.Add(new City() { Name = "Nagano", Country = "Japan", IsAccent = false });
            cities.Add(new City() { Name = "Zadar", Country = "Croatia", IsAccent = true });
            listBoxCities.ItemsSource = cities;
        }

    }
}

 

Start your app and here is result (cities from Europe are accented):

screenshot1

Complete working zip solution for Windows Phone 7 and 8 can be downloaded from here.

Nokia Places API for Windows Phone

April 16, 2012 18 comments

In this post I will demonstrate way to begin using the Nokia RESTful Places API and display places data in your Windows Phone application.

Nokia’s RESTful Places API is a web service API that allows you to offer your application’s users Nokia’s Places service as available in Nokia Maps and other applications within the look & feel of your own application. The Places service supports a variety of use cases that center around two major features:

    • Place discovery – Help users find the places relevant to them
    • Place information retrieval – Provide detailed information about places users are interested in

 

When you use the Places API to build an application, your users can find individual locations in

    • over 200 countries
    • over 1.5 million different areas (cities, districts, regions)
    • over 25 million streets split into 90 million and more individual segments
    • over 120 million point addresses within 15 countries
    • over 75 million named and categorized places in more than 200 countries

 

Several million of those places come with rich attribution/content like

  • editorial information for trusted content partners like Lonely Planet and Michelin
  • ratings, reviews and images aggregated from a variety of online communities
  • additional business information, often provided by the owner

In following scenario I will create WP7 app that will explore Nokia Places in your neighborhood, display them as pushpins on Bing Map, and show additional data (like images) on each pushpin tap. But first, in order to use API’s, you have to register as a Nokia Developer and retrieve your API Key. Also, you should register for Bing Maps Account, to submit credential data to your Bing Maps control:

  1. Register as a Nokia Developer
  2. Register Nokia Places API key
  3. Register Bing Maps application

Also, to consume Nokia Places API data you need an HTTP client and some Json framework for data deserialization. In this scenario I used RestSharp and Json.NET, and you can easily download nuget packages from following links:

(note: Json.NET was part of RestSharp package, but since version 103, Json.NET was removed from RestSharp as a dependency).

So, in this demo, we will collect current GPS location and ask Nokia Places service for nearby places:

private GeoCoordinateWatcher watcher;
private GeoCoordinate myLocation;
private string nokiaPlacesUrl = "http://demo.places.nlp.nokia.com/places/v1/discover/explore";
private string nokiaPlaceUrl = "http://demo.places.nlp.nokia.com/places/v1/places/";
private string nokiaPlacesAppID = "YOUR_APP_ID";
private string nokiaPlacesAppCode = "YOUR_APP_CODE";
private List<Place> places;
private bool ignoreMapTap = false;

public MainPage()
{
    InitializeComponent();
    watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);watcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(watcher_PositionChanged);
    watcher.Start();
}

private void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
    if (watcher.Status != GeoPositionStatus.Ready) return;
    myLocation = e.Position.Location;
    mapPlaces.SetView(myLocation, 14);
    watcher.Stop();
    this.GetPlaces(myLocation);
}

private void GetPlaces(GeoCoordinate location)
{
    string url = string.Format("{0}?at={1},{2}&app_id={3}&app_code={4}&tf=plain&pretty=true", nokiaPlacesUrl, location.Latitude, location.Longitude, nokiaPlacesAppID, nokiaPlacesAppCode);
    var client = new RestClient(url);
    var request = new RestRequest(string.Empty, Method.GET);
    client.ExecuteAsync<RestRequest>(request, (response) =>
    {
        places = this.ParsePlaces(response.Content);
        this.AddPushpins(places);
    });
}

Resulting Json response from service looks like this:

json1

 

In following method we deserialize result to list of Places objects:

private List<Place> ParsePlaces(string json)
{
    List<Place> places = new List<Place>();
    if (json == string.Empty)
        return places;

    JToken jToken = JObject.Parse(json)["results"]["items"];
    if (jToken == null)
        return places;

    IList<JToken> jlList = jToken.Children().ToList();
    foreach (JToken token in jlList)
    {
        Place place = JsonConvert.DeserializeObject<Place>(token.ToString());

        if (place.type == "urn:nlp-types:place")
        {
            IList<JToken> jTokenListPosition = token["position"].ToList();
            GeoCoordinate position = new GeoCoordinate();
            position.Latitude = double.Parse(jTokenListPosition[0].ToString());
            position.Longitude = double.Parse(jTokenListPosition[1].ToString());
            place.position = position;

            places.Add(place);
        }
    }

    return places;
}

 

Places are added to map in following method:

private void AddPushpins(List<Place> places)
{
    mapPlaces.Children.Clear();

    Pushpin pushpinMe = new Pushpin();
    pushpinMe.Background = new SolidColorBrush(Colors.Green);
    pushpinMe.Location = myLocation;
    pushpinMe.Content = "You";
    mapPlaces.Children.Add(pushpinMe);

    foreach (Place place in places)
    {
        Pushpin pushpin = new Pushpin();
        pushpin.Background = new SolidColorBrush(Colors.Blue);
        pushpin.Opacity = 0.6;
        pushpin.Location = place.position;
        pushpin.Tap += new EventHandler<GestureEventArgs>(pushpin_Tap);
        pushpin.Content = place.title;
        mapPlaces.Children.Add(pushpin);
    }
}

This is how it looks in our app:

Pushpins

When user taps one of pushpins, another call to service is requested.

private void pushpin_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
    ignoreMapTap = true;
    Pushpin pushpin = (sender as Pushpin);
    Place place = places.Where(p => p.position == pushpin.Location).FirstOrDefault();
    this.DataContext = place;

    stackPanelDetail.Visibility = Visibility.Visible;
    image.Source = null;
    string url = string.Format("{0}{1}?app_id={2}&app_code={3}", nokiaPlaceUrl, place.id, nokiaPlacesAppID, nokiaPlacesAppCode);
    var client = new RestClient(url);
    var request = new RestRequest(string.Empty, Method.GET);
    client.ExecuteAsync<RestRequest>(request, (response) =>
    {
        try {
            string firstImageUrl = JObject.Parse(response.Content)["media"]["images"]["items"].First["src"].ToString();
            image.Source = new BitmapImage(new Uri(firstImageUrl, UriKind.Absolute));
        }
        catch { }
    });
}

 

This call will return place’s details like alternative names, contact phones, fax, emails, websites, categories, rating, images etc (see image below):

json2

In our scenario, we will display place data and first of images in media collection:

EmpireStateBuilding_Screenshot2

Conclusion

You can use Nokia Places API in various scenarios. For example, you can create some kind on City Explorer application and use various data together with Windows Phone platform features like Bing Maps Direction Task, Email Compose Task, Phone Call Task, Share Link Task and Web Browser Task to provide great app experience. Nokia also provide more robust maps services like Nokia Maps, Nokia Directions and Nokia Traffic which can be easily combined with WP Browser control (see Nokia Places API Blog for more information about this integration).

Additional resources and references about Nokia RESTful Places API can be found in following links:

Nokia RESTful Places Overview

Nokia RESTful Places Quick Start

Nokia RESTful Places User Guide

Nokia RESTful Places API Reference

Complete working zip solution can be downloaded from here (don’t forget to put your Bing Maps credentials in MainPage.xaml and nokiaPlacesAppID and nokiaPlacesAppCode in MainPage.xaml.cs).

Categories: Windows Phone Tags:

How to stream video and audio from SkyDrive on Windows Phone

March 7, 2012 8 comments

In this post I will explain how to stream video and audio files from SkyDrive in MediaElement control or MediaPlayerLauncher. If you ever tried to play your media files from SkyDrive in your WP7 application using Live Connect API, you will probably get error message like “3002 AG_E_NOT_FOUND” or “Sorry, we can’t open this file on your phone“:

error1

error2

What is happening?

If you try to open your media file by pasting url in your desktop browser, you will notice that file url has format like:
http://storage.live.com/<folder_id>/MyVideo.wmv

But, when your default player starts streaming (e.g. Windows Media Player), you can see in playing file properties that it’s location is different; url is redirected to other location like:
http://frbedg.sn2.livefilestore.com/<folder_id>/MyVideo.wmv


On Windows Phone, for security reasons, the Silverlight runtime restricts access to certain classes of URLs classes in the System.Net and System.Windows.Controls namespace. These restrictions are designed to prevent networking threats (for example, threats based on a Silverlight application run from an internet server getting access to resources on a local intranet server). This means that MediaElement won’t redirect passed Source Uri value to other url address, and you will get error mentioned above. And this is not problem only with SkyDrive. You can experience this problem on other web-based hosting services.

The trick is to get second url by component that is not affected by this restriction, which is in our case WebBrowser control. All you have to do is to put WebBrowser control to your XAML page, set Visibility to Collapsed, and add handler to Navigated event:

<Grid x:Name="LayoutRoot" Background="Transparent" Margin="12,0,12,0">
 <MediaElement Name="movie" AutoPlay="True" Stretch="Uniform" />
 <phone:WebBrowser Name="webBrowser" Navigating="webBrowser_Navigating" Visibility="Collapsed" /> 
</Grid> 

Navigating event occurs every time before the WebBrowser control navigates to a new document. So, we only have to get uri when event triggers second time, and then set MediaElement Source property to that value, or launch MediaPlayerLauncher:

private void webBrowser_Navigating(object sender, NavigatingEventArgs e)
{
    if (e.Uri.ToString() != file.Source)
    {
        e.Cancel = true;
        if (launchInControl)
            movie.Source = e.Uri;
        else {
            MediaPlayerLauncher mediaPlayerLauncher = new MediaPlayerLauncher();
            mediaPlayerLauncher.Media = e.Uri;
            mediaPlayerLauncher.Controls = MediaPlaybackControls.All;
            mediaPlayerLauncher.Show();
        }
    }
}

And that’s it. Streaming will start. Happy coding.

Complete working zip solution can be downloaded from here (don’t forget to put your app client ID in MainPage.xaml).

Files

SamuraiJack

How to crash other WP7 applications?

February 20, 2012 6 comments

Do you know what is easiest way to crash someone’s WP7 application? Just quickly double-tap it’s “Rate my app” link or button. Marketplace Review page will display, but when you go back to app, it will disappear because it crashed.

Of course some app’s has this issue properly resolved, most not, but what happens when you double tap button or link that starts any kind of WP7 Launchers? Simply, your app will try twice to launch task, but second launch will throw “Navigation is not allowed when the task is not in the foreground” exception, because your app is not anymore active on phone.

Here is example. Create new Windows Phone application, uncomment ApplicationBar section at the end of MainPage.xaml and leave one ApplicationBarIconButton:

<phone:PhoneApplicationPage.ApplicationBar>
 <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
 <shell:ApplicationBarIconButton IconUri="/Home.png" Text="navigate"/>
 </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar> 

In MainPage.xaml.cs add following code:

ApplicationBarIconButton appBarButton;

public MainPage()
{
    InitializeComponent();
    appBarButton = ApplicationBar.Buttons[0] as ApplicationBarIconButton;
}

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    appBarButton.Click += new EventHandler(appBarButton_Click);
}

private void appBarButton_Click(object sender, EventArgs e)
{
    WebBrowserTask task = new WebBrowserTask();
    task.Uri = new Uri("http://www.bing.com");
    task.Show();
}

This app will launch the web browser application and display the Bing web site. Start app and double-tap application bar icon button. Following unhandled exception will occur:

appbarcrash

This will happen in most situation where you are trying to start any of available launchers such as Web Browser Task, Share Link Task, Marketplace Review Task, etc. There are several ways to handle those situations:

1. Add task.Show() method in try/catch block:

private void appBarButton_Click(object sender, EventArgs e)
{
    WebBrowserTask task = new WebBrowserTask();
    task.Uri = new Uri("http://www.bing.com");
    try {
        task.Show();
    }
    catch {}
}

2. Disable button on first click:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    appBarButton.Click += new EventHandler(appBarButton_Click);
    if ( e.NavigationMode == NavigationMode.Back )
        appBarButton.IsEnabled = true;
}

private void appBarButton_Click(object sender, EventArgs e)
{
    appBarButton.IsEnabled = false;
    WebBrowserTask task = new WebBrowserTask();
    task.Uri = new Uri("http://www.bing.com");
    task.Show();
}

This will disable button, so second click won’t happen, but you also have to re-enable button when user goes back to this page.

3. Remove Click handler:

private void appBarButton_Click(object sender, EventArgs e)
{
    appBarButton.Click -= new EventHandler(appBarButton_Click);
    WebBrowserTask task = new WebBrowserTask();
    task.Uri = new Uri("http://www.bing.com");
    task.Show();
}

Happy coding!

Windows Phone 7 Pivot Indicator

December 25, 2011 3 comments

In this post I will explain how to add simple indicator to Windows Phone 7 Pivot Page, which tells user how many Pivot Items are in page and which Pivot Item is currently active. Indicator control is implemented as set of small circles bellow title where one circle which indicates active pivot item is accented (see picture bellow).

pivot indicator_sm

Pivot Control is great MetroUI control that is equivalent of desktop Tab control. Pivot orders the elements logically, splitting the information into categories and listing the elements available in each category. It is an analytical list used to show different aspects of the same content. But sometimes developers make mistakes and use Pivot Control in incorrectly. Let’s take look in next example. What is difference between these two pages?

not pivot app_sm pivot app_sm

These two pages look same, but first page is classic WP7 page and second one is Pivot page. In this case user is not aware that there are more Pivot items, because developer added captions that are too long. And I seen cases like this in several WP7 apps. But sometimes we need two words for pivot item captions.
Also, because of pivot control simplicity (which is great) user is not aware how many pivot items are in page. Users simply swipe pivot items left or right, and sometimes it is useful to have indicator that describes better context of current pivot navigation. In that case you can use Pivot Indicator shown above (I saw this indicator in one application but I cannot recall which). Let me describe how to create this indicator.

Add UserControl to your Pivot project and name it Indicator.xaml. Add horizontally oriented StackPanel on it. It will be used to dynamically add circles on later.

<Grid x:Name=”LayoutRoot” >
<StackPanel Name=”ellipsesPanelOrientation=”Horizontal” />
</Grid>

Add following code to UserControl:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace PivotIndicator
{
public partial class Indicator : UserControl
{
/// <summary>
/// Public ItemsCount property of type DependencyProperty
/// </summary>
public static readonly DependencyProperty ItemsCountProperty =
    DependencyProperty.Register(“ItemsCount“,
    typeof(int),
    typeof(Indicator),
    new PropertyMetadata(OnItemsCountChanged));

/// <summary>
/// Public SelectedPivotIndex property of type DependencyProperty
/// </summary>
public static readonly DependencyProperty SelectedPivotIndexProperty =
DependencyProperty.Register(“SelectedPivotIndex“,
typeof(int),
typeof(Indicator),
new PropertyMetadata(OnPivotIndexChanged));

/// <summary>
/// Constructor
/// </summary>
public Indicator()
{
InitializeComponent();
}

/// <summary>
/// Gets or sets number of pivot items
/// </summary>
public int ItemsCount
{
set { SetValue(ItemsCountProperty, value); }
get { return (int)GetValue(ItemsCountProperty); }
}

/// <summary>
/// Gets or sets index of selected pivot item
/// </summary>
public int SelectedPivotIndex
{
    set { SetValue(SelectedPivotIndexProperty, value); }
    get { return (int)GetValue(SelectedPivotIndexProperty); }
}

/// <summary>
/// OnItemsCountChanged property-changed handler
/// </summary>
private static void OnItemsCountChanged(DependencyObject obj,     DependencyPropertyChangedEventArgs args)
{
(obj as Indicator).SetCircles();
}

/// <summary>
/// OnPivotIndexChanged property-changed handler
/// </summary>
private static void OnPivotIndexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
(obj as Indicator).AccentCircle();
}

/// <summary>
/// Draws circles.
/// </summary>
private void SetCircles()
{
ellipsesPanel.Children.Clear();
for (int i = 0; i < this.ItemsCount; i++)
{
Ellipse ellipse = new Ellipse() { Height = 10, Width = 10, Margin = new Thickness(2,0,0,0) };
ellipsesPanel.Children.Add(ellipse);
}
this.AccentCircle();
}

/// <summary>
/// Accents selected pivot item circle.
/// </summary>
private void AccentCircle()
{
int i = 0;
foreach (var item in ellipsesPanel.Children)
{
if (item is Ellipse)
{
Ellipse ellipse = (Ellipse)item;
if (i == this.SelectedPivotIndex)
ellipse.Fill = (SolidColorBrush)Application.Current.Resources["PhoneForegroundBrush"];
else
ellipse.Fill = (SolidColorBrush)Application.Current.Resources["PhoneDisabledBrush"];
i++;
}
}
}

}
}

We have two dependency properties; ItemsCount and SelectedPivotIndex, and two methods; SetCircles() – which is called each time when we add new pivot item, and AccentCircle() – which is called each time when another pivot item is selected.
Now add Indicator control to you Pivot page. Name your Pivot control as “myPivot” and add Pivot control header TitleTemplate:

<!–Pivot Control–>
<controls:Pivot Name=”myPivotTitle=”MY APPLICATION“>

<!–Pivot Control Header–>
<controls:Pivot.TitleTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text=”MY APPLICATION“/>
<my:Indicator x:Name=”Indicator1VerticalAlignment=”Top” >
<my:Indicator.ItemsCount>
<Binding ElementName=”myPivotPath=”Items.Count“/>
</my:Indicator.ItemsCount>
<my:Indicator.SelectedPivotIndex>
<Binding ElementName=”myPivotPath=”SelectedIndex“/>
</my:Indicator.SelectedPivotIndex>
</my:Indicator>
</StackPanel>
</DataTemplate>
</controls:Pivot.TitleTemplate>

    <controls:PivotItem Header=”first” />

    <controls:PivotItem Header=”second” />

    <controls:PivotItem Header=”third” />

</controls:Pivot>

In this way Indicator control is directly binded to Pivot control Items.Count and SelectedIndex property, and on each change of these properties circles in Indicator control will be automatically redrawn, even in design mode.
And that is it. If you have more than three pivot items decide if you want to add this simple, but efficient control to your pivot page.
Complete code with solution can be downloaded from here.

Categories: Windows Phone Tags: , , ,

Strange Launch time test results in Windows Phone Marketplace Test Kit

November 7, 2011 3 comments

With Windows Phone 7.1 (Mango) SDK Microsoft announced Windows Phone Marketplace Test Kit, which provides a suite of automated, monitored, and manual tests to help prepare your applications to be accepted in the Marketplace the first time you submit them. The test kit enables you to identify and fix issues prior to Marketplace submission, which will save your time in the submission process. The test kit is integrated within Visual Studio and you can open it by choosing the Open Marketplace Test Kit option in Project Menu. This test kit is a must for anyone trying to certify their app, because it really helps you in preparing your application for publishing on Marketplace.
The test kit contains the follow test categories:

  • Application Details
  • Automated Tests
  • Monitored Tests
  • Manual Tests

So, when I started with testing my application everything went smoothly; I prepared my screenshots, all automated test passed successfully. But when I run Monitored tests and Launch test failed, stating that my app took 5.9 seconds to start. Performance and Resource Management requirement: 5.2.1. Launch time (http://msdn.microsoft.com/en-us/library/hh184840(v=VS.92).aspx) states following:
“The application must render the first screen within 5 seconds after launch.”

Launch time test results

This was very strange for me since I thought that I did good code optimization work and my app in reality starts on device almost immediately (0.5 seconds). I started to investigate what caused this behavior, but with no improvement, even when I commented all code in my application. My application is simple pivot app with dozen of standard WP7 controls and on Bing Map control.
So I started from beginning with testing blank Windows Phone Application. Launch time result was 1.8 seconds. Then I tested blank Windows Phone Pivot Application, and result was 2.6 seconds. When I added Map control to pivot item Launch time was 4.5 seconds! Almost 5 seconds and I didn’t add any other assembly, control, line of code or anything else.
So this is problem. No matter that all these apps start immediately, for some reason Test Kit reports much higher numbers. Also, seems that Map control causes lot of work for UI thread during parsing and creation of Bing Map for Silverlight object from XAML and drawing it’s visuals during initialization. Microsoft has good article on MSDN regarding Performance Considerations in Applications for Windows Phone: http://msdn.microsoft.com/en-us/library/ff967560(v=VS.92).aspx. One of suggestions is to use Background Threads to avoid complex processes that can block the UI Thread (see article: How to use a Background Worker). So I decided to put background worker in page initialization and add Map dynamically in RunWorkerCompleted event:

        public MainPage()
{
InitializeComponent();

BackgroundWorker backroungWorker = new BackgroundWorker();
backroungWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backroungWorker_RunWorkerCompleted);
backroungWorker.RunWorkerAsync();
}

private void backroungWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Map map2 = new Map();
this.ContentPanel.Children.Add(map2);
}

Or for Pivot app:

private void backroungWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Map map2 = new Map();
this.PivotItemThree.Content = map2;
}

Launch test results were 1 second shorter than implementation in classic way by adding XAML code to your page. Here are results for all app types:

Application type Launch time results
Windows Phone Application [INFORMATION] : Application took 1.8 seconds to launch.
Windows Phone Pivot Application [INFORMATION] : Application took 2.6 seconds to launch.
Windows Phone Application with Map [INFORMATION] : Application took 4 seconds to launch.
Windows Phone Pivot Application with Map [WARNING] : Application took 4.5 seconds to start. This is close to the allowed limit for launch time being 5 seconds. For more information, see the Application Certification
Requirements for Windows Phone at http://go.microsoft.com/fwlink/?LinkId=223631.
Windows Phone Application with Map created on RunWorkerCompleted event [INFORMATION] : Application took 3 seconds to launch.
Windows Phone Pivot Application with Map on RunWorkerCompleted event [INFORMATION] : Application took 3.5 seconds to launch.

If you have issues of this type consider using of dynamically adding controls to your main page on Background Thread. I am not sure is this behavior of Test Kit same in official Marketplace submission process, since real app response time and time from test results are really different. Also, I conducted these tests only on my machine and my device (LG E900 Optimus), so I am not sure is this common behavior in all development environments. Please leave comment with your results on Launch time tests.

Backup your Windows Phone Isolated Storage data to SkyDrive using Live Connect API

September 15, 2011 31 comments

As a Windows Phone developer you know that you can store your application local data to Isolated Storage. Isolated Storage restricts all your I/O operations so other applications cannot access or corrupt your data, which provides very efficient security of app’s and user’s data. You can store WP7 local data to IsolatedStorageSettings, database or folders and files.

You also know that you have to backup your data in case of data loss or something else. Classic backup scenario includes simple data upload to your web application or cloud service. But if you tried to implement solution that includes upload to SkyDrive, DropBox or any other free cloud hosting service, you know that situation is not so simple, primarily because there are not any official API providers for Windows Phone platform. Off course, there are some community libraries that can help you, but in most cases they are not suitable for WP7 solutions or are very hard to implement. For example, there is community SkyDrive API library called SkyDrive .NET API Client, based on SkyDrive WebDAV web services, which works fine in web or desktop environment, but not for WP7. SkyDrive’s problem was lack of official Microsoft documentation and API. But few months ago Microsoft finally announced REST APIs for Windows Live platform. It was called “Messenger Connect”, but problem was that was primarily targeted to web client script developers, because APIs were exposed as JavaScript APIs. But no matter what, it was good sign. It was matter of time when we can expect managed (C#) API for Windows Live. On September 13th, Microsoft as a part of BUILD conference preliminary release of Windows 8, also published Live Connect, the new API that supports Metro style apps and Windows Phone apps. You can check out Live Connect now by visiting http://dev.live.com. Final release of Live Connect is expected in November 2011. You can download Live SDK Developer Preview from here.

 

Referencing the APIs with C#

When you download and unpack Live Connect SDK Developer Preview you will find Live Connect MSI setup file (LiveSDK.msi) and preliminary CHM documentation (LiveConnectPrelim.chm). When you install LiveSDK, you will find two new assemblies in Visual Studio .NET reference list: Microsoft.Live and Microsoft.Live.Controls:

image

Also, API ships with Live Connect Sign In control that will help you with user sign-in scenarios. If the control is not visible in your Toolbox you can register it by browsing Windows Phone\References\Microsoft.Live.Controls.dll assembly in Live SDK installation folder.

image

 

Getting a client ID and configuring your app

Before you continue with your development and call Live Connect APIs, you must configure your app with a unique identifier, which we call a client ID. You get a client ID and you can specify a redirect domain (if needed), at the Live Connect app management site. Sign in with your Microsoft account credentials, click Create application, type the app’s name and then click I accept. Live Connect creates and then displays the client ID. It should look something like this 00000000603E0BFE:

image

image

 

Sign-in user

To enable user to upload data to SkyDrive (or use any other Windows Live service like Hotmail, Contacts, Messenger or settings), the user must be signed in to Live Connect. A standard way to do this is by adding sign-in functionality to your app. This functionality (typically a button or a hyperlink) enables a user to provide their Microsoft account credentials. After the Live Connect APIs verify these credentials, it returns an access token to your app. This access token confirms that the user successfully signed in, and it specifies which parts of the user’s info your app can work with and for how long.

image

Best way to prompt a user to sign in in your WP7 app is to use the Live Connect sign-in control. You do this by setting the sign-in control’s properties in XAML code as is shown in code above. You must declare following namespace in your XAML page:

<phone:PhoneApplicationPage 
    …

xmlns:my="clr-namespace:Microsoft.Live.Controls;assembly=Microsoft.Live.Controls">

Also, you must set the Scopes, ClientId, RedirectUri and SessionChanged properties of Sign in control:

Scopes defines permissions from the user who owns the info to access his or her info or to create new objects on his or her behalf. In our scenario wl.skydrive_update scope is required to access and update user’s SkyDrive folders and files.

ClientId property is client ID of your Windows Live Application.

RedirectUri property defines landing page for you application. However, in Windows Phone scenarios that is not needed. In our case we will set RedirectUri in SignIn control to https://oauth.live.com/desktop.

SessionChanged property sets event handler method. Here is example how to set SessionChanged and LoginCompleted events in SignIn control that you put into your XAML page:

 

void btnSignin_SessionChanged(object sender, LiveConnectSessionChangedEventArgs e)
{
    if (e.Session != null &&
        e.Session.Status == LiveConnectSessionStatus.Connected)
    {
        client = new LiveConnectClient(e.Session);
        infoTextBlock.Text = "Signed in.";
        client.GetCompleted +=
            new EventHandler<LiveOperationCompletedEventArgs>(btnSignin_GetCompleted);
        client.GetAsync("me", null);
    }
    else
    {
        infoTextBlock.Text = "Not signed in.";
        client = null;
    }
}

void btnSignin_GetCompleted(object sender, LiveOperationCompletedEventArgs e)
{
    if (e.Error == null)
    {
        infoTextBlock.Text = "Hello, signed-in user!";
    }
    else
    {
        infoTextBlock.Text = "Error calling API: " +
            e.Error.ToString();
    }
}

For more information about user’s sign in, check “Signing users in” chapter in Live Connect documentation.

So, if you successfully set your XAML page and sign in code, following screenshots show user’s “sign in scenario”:

1) Click Sign in button:

image

 

2) User is redirected to Windows Live authentication page:

image

 

3) User is prompt to okay scopes that your application needs:

image

 

4) User is signed:

image

 

Upload data to SkyDrive

In this scenario I used Camera Capture feature of WP7 to capture camera image, save it to Isolated Storage and upload it to dedicated SkyDrive folder. CameraCaptureTask is used to capture desired image and save it to Isolated Storage. Following code describes capturing and image saving to Isolated Storage:

private LiveConnectClient client;
private CameraCaptureTask cameraCaptureTask;
private string cameraRollFileName = "MyCameraRollPicture.jpg";
private string skyDriveFolderName = "IsolatedStorageFolder";
private string skyDriveFolderID = string.Empty;

private void Camera_Click(object sender, EventArgs e)
{
    try
    {
        cameraCaptureTask.Show();
    }
    catch (System.InvalidOperationException ex)
    {}
}

private void cameraCaptureTask_Completed(object sender, PhotoResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        BitmapImage bmp = new BitmapImage();
        Stream capturedImageStream = e.ChosenPhoto;
        bmp.SetSource(capturedImageStream);
        this.SaveImageToIS(bmp);
    }
}

private void SaveImageToIS(BitmapImage bitmapImage)
{

    Uri uri = new Uri(cameraRollFileName, UriKind.Relative);
    string file = uri.ToString();
    using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
    {
        if (store.FileExists(file))
            store.DeleteFile(file);
        IsolatedStorageFileStream fileStream = store.CreateFile(file);
        StreamResourceInfo sri = null;
        sri = Application.GetResourceStream(uri);
        WriteableBitmap wb = new WriteableBitmap(bitmapImage);
        Extensions.SaveJpeg(wb, fileStream, wb.PixelWidth, wb.PixelHeight, 0, 85);
        fileStream.Close();
    }
}

When user captures photo and clicks upload button, following scenario is happening:

1) Get info about user’s SkyDrive folders:

 

private void UploadPhoto_Click(object sender, EventArgs e)
{
   
    if (client == null || client.Session == null || client.Session.Status != LiveConnectSessionStatus.Connected)
    {
        MessageBox.Show("You must sign in first.");
    }
    else
    {
        client.GetCompleted += new EventHandler<LiveOperationCompletedEventArgs>(GetFolderProperties_Completed);
        // If you put photo to folder it becomes album.
        client.GetAsync("me/skydrive/files?filter=folders,albums");
    }
}

GetAsync is a helper function for making calls to the REST API. In our case we will get data about all folders and albums in user’s SkyDrive root folder.

 

2) Process data returned by GetCompleted handler:

private void GetFolderProperties_Completed(object sender, LiveOperationCompletedEventArgs e)
{

if (e.Error == null)
{
    Dictionary<string, object> folderData = (Dictionary<string, object>)e.Result;
    List<object> folders = (List<object>)folderData["data"];

    foreach (object item in folders)
    {
        Dictionary<string, object> folder = (Dictionary<string, object>)item;
        if (folder["name"].ToString() == skyDriveFolderName)
        skyDriveFolderID = folder["id"].ToString();
    }

    if (skyDriveFolderID == string.Empty)
    {
        Dictionary<string, object> skyDriveFolderData = new Dictionary<string, object>();
        skyDriveFolderData.Add("name", skyDriveFolderName);
        client.PostCompleted += new EventHandler<LiveOperationCompletedEventArgs>(CreateFolder_Completed);
        client.PostAsync("me/skydrive", skyDriveFolderData);
     }
     else
        UploadFile();
}
else
{
     MessageBox.Show(e.Error.Message);
}
}

REST will return “data” text value which holds information about SkyDrive root folder. Convert “data” information into collection of folders and iterate them to check if desired folder exists. In this case it is value of variable skyDriveFolderName.

 

private string skyDriveFolderName = "IsolatedStorageFolder";

If folder exists we will took its folderID value, because that value is needed to define upload folder in next steps. If folder does not exists, we have to create it using PostAsync method. When folder is created following method for handling CreateFolder_Completed event is executed and new folder ID value is assigned to skyDriveFolderID variable:

 

private void CreateFolder_Completed(object sender, LiveOperationCompletedEventArgs e)
{
    if (e.Error == null)
    {
        infoTextBlock.Text = "Folder created.";
        Dictionary<string, object> folder = (Dictionary<string, object>)e.Result;
        skyDriveFolderID = folder["id"].ToString();
        UploadFile();
    }
    else
    {
        MessageBox.Show(e.Error.Message);
    }
}

3) Upload image to SkyDrive’s "IsolatedStorageFolder" folder:

When we are sure that desired folder exists in user’s SkyDrive, last step is uploading file from Isolated Storage.

 

private void UploadFile()
        {
            if (skyDriveFolderID != string.Empty)
            {
                string[] filePathSegments = cameraRollFileName.Split(‘\\’);
                string fileName = filePathSegments[filePathSegments.Length - 1];
                var scopes = new List<string>(1);
                this.client.UploadCompleted
                    += new EventHandler<LiveOperationCompletedEventArgs>(ISFile_UploadCompleted);

                IsolatedStorageFileStream fileStream = null;
                try
                {
                    using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        fileStream = store.OpenFile(cameraRollFileName, FileMode.Open, FileAccess.Read);
                    }

                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                }
                this.client.UploadAsync(skyDriveFolderID, cameraRollFileName, true, fileStream, null);
            }
        }

private void ISFile_UploadCompleted(object sender, LiveOperationCompletedEventArgs args)
{
    if (args.Error == null)
    {
        Dictionary<string, object> file = (Dictionary<string, object>)args.Result;
        // For some reason SkyDrive link property is not part of result.
        this.infoTextBlock.Text = "File uploaded.";
    }
    else
    {
        this.infoTextBlock.Text =
            "Error uploading file: " + args.Error.ToString();
    }
}

And this is it.

User is notified that file is uploaded:

image

 

If you check you SkyDrive account, new folder with image will be there:

image

 

Complete working zip solution can be downloaded from here (don’t forget to put your app client ID in MainPage.xaml).

You can download Live SDK Developer Preview from here.

Follow

Get every new post delivered to your Inbox.