Thursday, April 14, 2011

Typing while debugging WP7 projects

If you are developing for WP7, you'll notice that edit and continue is not supported. That's a bit of a bummer, but it's far less annoying than the constant dialog that pops up to tell you that it's not enabled.

A two second trip into Tools->Options->Debugging|Edit and Continue to disable it will make the dialog go away. This lets you type in the editor while debugging in the emulator!

Tuesday, March 29, 2011

Getting "clicks" right on the phone

It turns out that how we handle "click" in the desktop world in some cases isn't appropriate for the phone world. The mouse is by far a more precise tool for selection - we can click on things 16-20px high, but the UX guidelines for Metro suggest a min height of 48px. In the desktop world, you mouse down on a button, it shows some sort of pressed button effect, you mouse up, and if the mouse stayed within the bounds of the button then you do the work in the click event.


Problem 1: what to do about lack of pressed effects in Metro
In metro there are few hover/pressed effects - so that raises a question - how does the person know on "finger" down that they have touched the right thing? The choice remains between showing a hover effect OR doing the work immediately on finger down instead of finger up. Doing nothing leaves the user holding the device with their finger pressed to the screen wondering what's happening.


Solution: always do something when the finger touches a clickable area - invert the colors, move it up, or do the work straight away.

Problem 2: Where you think you've clicked, you haven't.
This was one of my main usability challenges: in my app I would show a link whenever they made a word - this would clear the board, add to the word list and reset the tiles. The problem was the hyperlink control is so small by default that it is nearly impossible to click.

Also depending on the angle you are viewing the device, your eye might think you are clicking on something, when in fact you are touching somewhere completely different.



Solution: keep the clickable things large and finger shaped - there's a good reason why many of the buttons are circles. For the HyperlinkButton shown, I set the background to Transparent and made sure it was at least 48 px tall. Making backgrounds transparent extends the clickable area of a control. I spent many iterations squishing out every spare pixel to push it away from the hardware keys and away from the solution area. If I had my druthers, I would have changed it to a circular button.


Problem 3: Keep the clickable stuff away from the bottom edge of the screen.
The second frustration for people was accidentally pushing the back, search or windows key while attempting to add to the word list. It's slightly problematic that the link is close to those buttons, but having chosen the panorama - I lost 1/3 of my vertical real estate.


Solution: place clickable areas as far away from the hardware keys as you can. Implement tombstoning - i.e. think about the people who accidentally grab the phone in the wrong spot - make the state go exactly where they left off. They will be so pleased to know it was as they left it - I know because I've seen the OMG-what-did-I-push freakout happen many a time!


Conclusion: When you're adding stuff that clicks - it is so important to get the interaction right. Hand your app to your friends, see if they are confused. If you get the interaction right, they may never notice - but if you get it wrong, they'll think they can't use the phone.

Monday, March 21, 2011

Some great posts about Metro UX

I stumbled across a few great blogposts today, wanted to share them:

Thursday, March 17, 2011

Genuinely astonished! Nine Lettuce wins its first award!

Anyone who knows me knows I'll do practically anything for a free t-shirt! When I entered nine lettuce into the dev vs dev competition - I was stunned to find that it had won first place! Congrats to everyone, all of the apps looked great!

Thursday, March 10, 2011

Retrofitting the Trial API

I was nearly ready to ship my application when my friend asked me whether I intended to use the trial feature. Originally, I had decided against it, but having been convinced by watching a video he suggested I thought it was worth giving it a go.

I didn't really want to change very much - being so close to done at this point. I also wanted to allow for enough of the game to be played without nagging people to purchase it. After thinking about it for a little while, I decided to disable the new game button in trial mode.

The IsTrial() APIs

Disabling the buttons was consistent with the "Flowerz" experience from the above video and would be relatively easy to implement. To implement it's essentially:



using Microsoft.Phone.Marketplace;
public LicenseInformation licenseInfo = new LicenseInformation();
private void NewGame_Click(object sender, EventArgs e) {
if (!licenseInfo.IsTrial()) {
GameFactory.CreateNewGame();
}
else {
// do something that asks to buy the product.
}
}


Now note there are warnings all over MSDN that calling IsTrial() can take about 60ms - so you should consider caching it. However, since I was only going to call it on a button push, and not in a game loop, I didn't have to worry as much - people can tolerate about a 200ms - 500ms delay on a button push before noticing that work hasn't happened.

What to display when buying the app

I went to the howto MSDN page for the Trial API to get started. I am not normally this critical of an MSDN article - but I think these articles are based on pseudo code. They just don't deal in the real world problems that you run into using the API - which is unusual for MSDN, they're usually pretty good about this stuff.

Here is the dilemma I ran into: The suggested workflow of buying an app involved navigating to a second page, then offering to try or buy the application.





I started down the path of creating a "buy" page, which I would ask the frame to navigate to when someone pushed the new game button in trial mode. Because I'd already done a "share" page, I just copied that and started modifying. I went as far as wiring in the Marketplace task on a button push so I could send people to the right spot to buy the app.

I made two discoveries at this point - this little bit of code:

using Microsoft.Phone.Tasks;
public MarketplaceDetailTask detailTask = new MarketplaceDetailTask();
private void buy_now_Click(object sender, RoutedEventArgs e){
detailTask.Show();
}


..shows an error under the debugger (the marketplace does not like to be shown with the USB plugged in) - and even running standalone it wont work (obviously cause the app isn't in the store yet).

This was when I started to suspect the presence of untested pseudocode:
For the “Buy Now” button behavior described in our example, your code might look something like this:

...so I went in search of more information. At this point I stumbled across the second article, about testing and debugging your trial applications.

I had just been burned by tombstoning and the loaded event - especially in the "share" page which I had just copied to make my new "buy" page. If someone bumped the search button while I was navigated to the share page, when they pressed the back button to get back to nine lettuce, my app would be ressurected while it was navigated to a different page. I needed to make sure that backing out would load up my main panorama correctly.

Getting "share" right with tombstoning took effort, and I was keen to be able to test the same sorts of situations with the new "buy" page. All these questions started rattling through my head about the transition out of the MarketplaceDetailTask - questions I couldn't really test out as a developer...





I didn't like the solution in how to debug and test your trial apis because the magical box in step two was replaced by a MessageBox.Show. This would neither simulate tombstoning nor simulate starting the application over again with IsTrial() now set to false.

In the end I resolved that I needed a solution that would be tombstoning safe. So I deleted my "buy" page from the project. Instead of navigating to a second page which I would have difficulty testing, I decided to fade in a button on the panorama control. This eliminated my navigation issues and made something I could easily test.

I wound up writing a TrialManager that behaves differently in debug and release. In both it calls the MarketplaceDetailTask. This is important to do even if it throws an error in debug because you need to test having your app relaunch after purchase.

Essentially in DEBUG, I write a file when MarketpaceDetailTask.Show() is called - when the app resumes I implement IsTrial() to check for the file in DEBUG. The emulator doesn't remember files if you close it, so it's a great way to always be able to test the trial API but get past it. The device however will remember files between debugging sessions, so I also have a Reset method to clear the debug license off the phone.

Here is how I implemented a Debug version of the Trial APIs that test tombstoning after calling the MarketplaceDetailTask:




And the code for my TrialManager, which I think is more practical than the code in MSDN. Feel free to use and modify in your application.


using Microsoft.Phone.Tasks;
using System.IO.IsolatedStorage;

namespace NineLettuce {
internal static class TrialManager {
#if DEBUG
private const string LicenseFileName = "DebugFullLicense.txt";
#endif
public static bool IsTrial {
get {
#if DEBUG

System.Threading.Thread.Sleep(60); // simulate delay of IsTrial() call
// check if we have a license on the app.
IsolatedStorageFile myStore = IsolatedStorageFile.GetUserStoreForApplication();
return !(myStore.FileExists(LicenseFileName));
#else
// in release use the real APIs
LicenseInformation licenseInfo = new LicenseInformation();
return licenseInfo.IsTrial();
#endif
}
}

public static void ShowMarketplace() {
MarketplaceDetailTask detailTask = new MarketplaceDetailTask();
detailTask.Show();
#if DEBUG
// if tombstoning occurs, write out a file so
// we can test getting past trial mode
IsolatedStorageFile myStore
= IsolatedStorageFile.GetUserStoreForApplication();
myStore.CreateFile(LicenseFileName);
#endif
}

public static void ResetTrial() {
#if DEBUG
// function for clearing off the license
IsolatedStorageFile myStore
= IsolatedStorageFile.GetUserStoreForApplication();
if (myStore.FileExists(LicenseFileName)) {
myStore.DeleteFile(LicenseFileName);
}
#endif
}
}
}


Conclusions

Keep the buying experience simple, you don't need to navigate places to have a good experience in purchasing. If I had my druthers I would have put the new game button on the first page so it was more obvious.

If you want to do more elaborate things than I have done, you can use the IsolatedStorageFile solution to remember session state. If IsTrial() is false, and you've got something scribbled in a file, then you know you can show a particular screen or advance to a particular level.

I hope that as time goes on the developer experience in this area improves, as even with this solution it is still difficult to know what the experience will be when the app hits the store!

Related reads: Nine Lettuce, Beware the Loaded Event, Tech-Ed Video, How to Implement the Trial Experience in a Windows Phone Application, How to Test and Debug the Trial APIs

Sunday, March 6, 2011

Beware the loaded event

The biggest source of bugs I had with my application centered around use of the Loaded event.

Problem 1: Performance

The more pages your Panorama has – and the more complex the pages are, the slower it will be to load. In my application, each screen of the panorama is its own UserControl backed by the same GameViewModel.



Due to the complexity of the screens, I would load the application and it would appear to freeze on the last screen for a few moments before navigating to the first screen.

I couldn’t find a good profiler (that didn’t make me add code to my project), so I intensively used Stopwatch and Debug.WriteLine to figure out where my app was spending most of its time (knowing that adding the Debug.Writelines, observing under the debugger and running in Debug, not release would all be components that would slow down my app).

After dividing and conquering, I found that deferring the load of PanoramaItems you couldn’t see on startup helped the most.

My first attempt to solve the issue was to push work to the loaded event. This actually made the problem worse, not better. Having been involved with a number of frameworks over the years, I know that the Loaded event is always tricky to get right – the focus is mainly on getting the event fired – not necessarily at a time when it’s most convenient to do work.

Instead I deferred adding the guts of the panorama items until *after* the Loaded event had fired. This had a very visible improvement.

Here’s how the games screen does it:


<controls:Panorama Title="nine lettuce" Name="panoramaControl">
<!--[1] Create an empty panorama item -->
<controls:PanoramaItem Name="gameItem"
Loaded="GameScreen_Loaded" Header="games"/>
<controls:Panorama/>

In the code behind file - sync the loaded event, and ask to load the screen later using the dispatcher.

private void GameScreen_Loaded(object sender, RoutedEventArgs e) {
// [2] sync the loaded event, but defer the
// load until later
Dispatcher.BeginInvoke(new Util.MethodInvoker(LoadGameScreen));
}


private void LoadGameScreen() {
// [3] create the game screen user control
GameScreen game = new GameScreen();
// [4] put it into the PanoramaItem created in step [1]
gameItem.Content = game;
}

     
// handy utility method if you don’t
// like anonymous delegate syntax in step [2]
internal static class Util {
public delegate void MethodInvoker();
}


As with everything, once I’d figured it out, I found this performance article, confirming my suspicions about the Loaded event.

Problem 2: Event handler subscriptions in the Loaded and Unloaded events.

I have one GameViewModel for the entire application, and I put a static event on it called GameViewModelChanged. Of course the pitfall with static events is that they are pretty close to the #1 source of memory leak in a .net application.

So I was being very conscientious and dutifully unsubscribed from the event in the Unloaded event. However I wasn’t as careful about where I subscribed to it from, and it turns out in the phone this matters quite a bit.

The class of bug I had was pressing the Search button, then the Back button, and resuming my application. All my event handlers were lost!



Showing the effects of tombstoning on how nine lettuce gets loaded up.



The code below shows the kind of bug I had all over the place. After the Back button was pressed, the GameScreen no longer responded to changes to the GameViewModel.

The reason? My old GamesScreen was being shown again, but I’d unsubscribed everything when the search page was shown.


public partial class GameScreen : UserControl {

public GameScreen() {
InitializeComponent();
this.Unloaded += new RoutedEventHandler(Game_Unloaded);

// [1] Bug – this does not get called after the
// back button is pressed giving us an object
// where the unloaded code has run and undone
// what we did in the constructor.
GameFactory.GameViewModelChanged +=
GameFactory_GameViewModelChanged;
}

void Game_Unloaded(object sender, RoutedEventArgs e) {
// [2] unsubscribing from a static event
// to avoid memory leaks...
GameFactory.GameViewModelChanged -=
GameFactory_GameViewModelChanged;
}
}

[2] The code in Unloaded disconnects the object from the GameViewModelChanged event.

When the game is resumed, the code in [1] is not called.


The solution
The solution is simple: if you do something in Unloaded, reapply it in the Loaded event.

public partial class GameScreen : UserControl {
public GameScreen() {
InitializeComponent();
this.Loaded += new RoutedEventHandler(Game_Loaded);
this.Unloaded += new RoutedEventHandler(Game_Unloaded);
}

void Game_Loaded(object sender, RoutedEventArgs e) {

// [1] Fixtit! make sure your loaded
// and unloaded handlers match each other –
// if unloaded unsubscribes, make loaded subscribe.

GameFactory.GameViewModelChanged +=
GameFactory_GameViewModelChanged;

}
void Game_Unloaded(object sender, RoutedEventArgs e) {
GameFactory.GameViewModelChanged -=
GameFactory_GameViewModelChanged;
}
}
[1] - now the event handler will be hooked back up when our app is resuming.


Conclusions
On the phone - load time is crucial - if you're not using something right away, it's good to figure out how to do it later.

If you've got code in your constructors, Loaded, or Unloaded events, it's good to test pressing the search button and then the back button in your app.



Related reads


Thursday, March 3, 2011

Men use thumbs, women use index fingers?

This is totally unscientific as I had a sample group of 5 people...

Of the 2 guys and 3 girls I had testing my app, I found that they all held the device very differently. The two right-handed guys I gave it to all held the phone in their right hand and used their thumb to to move the pieces on the board. The girls held the device in their left hand and used their index finger to move the pieces.

I use the index finger to play - mainly because my hand is too small to hold and operate the device... it's the same reason I'm not awesome at playing the guitar!

When building my game I had to test that my buttons and hyperlinks were wide enough to work with both the thumb and index finger, as well as making sure the tile I was dragging was visible when dragged with either finger.

For the tile during drag, I subtracted the tile height and a little bit of buffer so that the tile would remain above the finger during a drag. This was a process of trial and error until it felt "right".

The best way to see what I mean is to give it a go!