Archive

Posts Tagged ‘WP7’

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.