Tuesday, April 29, 2014

You can only use State between OnNavigatedTo and OnNavigatedFrom in Windows Phone

If you've ever seen this error when debugging a Windows Phone app you have probably learned that you can't reference the State object* outside of OnNavigatedTo() or OnNavigatedFrom().  Easy fix!  Move those calls to the right methods.  If, for some reason, you can't act on that data at that moment, get the value that you need, stuff it in a field/property, then use it later.

The bigger frustration is when you do this but still get the error.  In other words, if you check and double-check, but you definitely only use State in the navigation methods.  If this happens to you, I might have the solution!

I recently came across this issue and was pretty frustrated at first, until I thought about what I was doing a bit more:

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    var x = await Something();
    var y = State["key1"];
    // And so on...
}

In this example, I have some pretty basic stuff.  I make a call to do something asynchronously, I get the state value, I do something with that value, etc.  But then it hit me.  That darn awake!  If you remember how await works, you'll realize that this can't really work.  The "secret" of await is that the code executes in two passes.  Everything up to the await runs in the context of the actual function.  After the async line, the containing function (OnNavigatedTo) actually returns.  Once the awaited function returns an actual value, the original code runs on a callback with access to the return value.  A highly simplified view is here:

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    Something( (x) =>
    {
        // This is actually a callback invoked when Something is done
        var y = State["key1"];
        // And so on...
    });
}

So the actual flow that is OnNavigatedTo calls Something, then returns.  When the callback is invoked, the runtime correctly complains that the usage of State isn't really happening in OnNavigatedTo.

So what's the solution?  Well, that depends on what you are doing.  In my case, I had a number of initializations to do, and I could just change the order to access State first.  Actually, that's the easiest!  Just access it immediately when the method starts and you'll probably be fine.  There's nothing wrong with an async section in there, as long as you only access State beforehand.  I hope this helps someone else!

* If you've never used the State object, it's pretty handy.  It's just a dictionary, but you can use it to store values in the current page that persist navigation/back, even if you leave the app and come back.  It gets cleared whenever the app is started.

Monday, April 28, 2014

Formatting text to view in Windows Phone Internet Explorer

Sometimes you need to present formatted text to users in your apps, and it's a pretty big pain to implement your own formatting engine.  Rich Text is sometimes an option, but if you are starting with HTML formatted data then the obvious choice is a WebBrower control (even if it is a little heavy as a control).  You probably already know how to use the NavigateToString() method to load am HTML formatted string.  If not, there's little to it:

ctrl.NavigateToString("<i>This should be in italics</i>");

Notice it's from code, not something you can do from XAML.  The problem is that you end up with a plain white background and black text, regardless of phone theme.  If you'd like the text to match the phone theme, all it takes is a little CSS magic!

Theme Colors

The first step is to get the theme colors.  There are typically two colors you want: the background and the foreground.  These (and other built-in static style/theme resources) can be retrieved with the Resources collection attached to the application: App.Current.Resources

What you want are the SolidColorBrush resources named PhoneBackgroundBrush and PhoneForegroundBrush.  Anything you get from the Resources collection is an object, so you need to cast them accordingly:

SolidColorBrush bg = (SolidColorBrush)App.Current.Resources["PhoneBackgroundBrush"],
    fg = (SolidColorBrush)App.Current.Resources["PhoneForegroundBrush"];

CSS

The next step is to create a CSS snippet to reference the color values.  This ensures that you don't need to modify the original HTML string directly.  All you set is color and background-color.  This will affect any text that doesn't have its own local style override.  The CSS looks like this:

<style type='text/css'>body {{color: rgb({0}, {1}, {2}); background-color: rgb({3}, {4}, {5}); font-size: inherit; font-family:Segoe WP}}</style>

I couldn't find a simple way to spit out the #RRGGBB color, so I made it easy on myself and used the CSS rgb() function to take the three values separately.

HTML

Finally, just wrap the original text with the CSS and a basic HTML body.  Obviously if you are working with HTML that is already a complete document, it won't be this simple, but this works great with snippets!

var html = "<html><head><meta name=\"viewport\" content=\"width=device-width\" /><title></title><style type='text/css'>body {{color: rgb({0}, {1}, {2}); background-color: rgb({3}, {4}, {5}); font-size: inherit; font-family:Segoe WP}}</style></head><body>{6}</body></html>";

return string.Format(html, fg.Color.R, fg.Color.G, fg.Color.B, bg.Color.R, bg.Color.G, bg.Color.B, txt);

For my needs, I found it useful to include a viewport declaration too.  This ensures that the content properly fills the screen and doesn't need to be user-scaled right away.  You may or may not need it.

SOLUTION

The final version can be made into a simple function to pass the HTML to, then to the control:

string PrepareText(string txt)
{
    SolidColorBrush bg = (SolidColorBrush)App.Current.Resources["PhoneBackgroundBrush"],
        fg = (SolidColorBrush)App.Current.Resources["PhoneForegroundBrush"];

    var html = "<html><head><meta name=\"viewport\" content=\"width=device-width\" /><title></title><style type='text/css'>body {{color: rgb({0}, {1}, {2}); background-color: rgb({3}, {4}, {5}); font-size: inherit; font-family:Segoe WP}}</style></head><body>{6}</body></html>";
    return string.Format(html, fg.Color.R, fg.Color.G, fg.Color.B, bg.Color.R, bg.Color.G, bg.Color.B, txt);
}

var html = PrepareText("<i>This should be in italics</i>");
web.NavigateToString(html);

NEXT STEPS

This is a pretty good way to get HTML-formatted text to look more like native text in an app. If you are going for an ebook reader type of text presentation, then you might want to also create a helper class for this and create properties for the foreground and background colors.  That would make it easier to support a cream background, or a sepia foreground for better readability.

Wednesday, April 16, 2014

Vote for Damian's game!

My son Damian and his friend Ben have created a great HTML 5 game, Outlaw of Gravity.  It works cross-browser including mobile browsers.


This was created for a contest, so please give it a play (it's quite fun and addictive!) and rate them five stars.  You won't be sorry!

Direct link: http://clay.io/game/outlawofgravity